rebbitMQ 学习
开始玩rebbitMq之前
需要下先安装 Erlang 和 RabbitMQ server
因为RabbitMQ 是建立在Erlang OTP平台上的,所以在安装RabbitMQ之前,我们需要先安装Erlang
由于在window10 上面开始玩的 所以 安装步骤只有windows 没有linux
Linux安装参考:RabbitMQ的安装与客户端的简单实用 - heyLuer - 博客园 (cnblogs.com)
未测试过 但是可以看看
第一步:下载并安装erlang
- 安装除了安装位置 其余全默认 安装完事儿后要记得配置一下系统的环境变量。
- 此电脑-->鼠标右键“属性”-->高级系统设置-->环境变量--> 可新建 可直接放在path里 路径是*\bin
- 最后windows键+R键,输入cmd,再输入erl,看到版本号就说明erlang安装成功了。
第二步:下载并安装RabbitMQ
- 安装完成后在开始目录下找到命令窗口
- 执行 rabbitmq-plugins enable rabbitmq_management
-
这个命令的意思是安装RabbitMQ的插件。
-
是否安装成功就看能否访问对应的地址:http://127.0.0.1:15672/
- 能打开界面 就说明前置条件已经弄好了 初始账户/密码:guest/guest
安装好之后 就可开始玩消息队列了
目前是五种模式 :基础模式、worker模式、pub/sub模式、rounting路由模式、topic模式
参考网站:Java开发之rabbitMQ的使用_范亚明的博客-CSDN博客
rabbitMQ五种模式使用方式以及springboot整合rabbitMQ的使用 - 名难 - 博客园 (cnblogs.com)
个人初始学习看法:
- 基础模式和worker模式
- 这两个模式在我看来是一样的,生产者执行发出消息请求,请求中带有队列code,消息会存放在rebbitMq的队列中,消费者执行后会从对应的队列中获取到消息,
- 重点:这两个消息是属于广播模式也就是只要消费者请求对应的队列就会获取到消息。
- 差别:worder模式在消费者端会有消息确认机制,目前测试是如果报错会重新下发消息。普通的没有消息确认机制,也没有测试过批量发送消息。
- 这两个模式请求顺序无所谓,对结果不造成影响都能获取到消息。
- pub/sub模式、rounting路由模式、topic模式
- 这三个模式重点就在于交换机和对应的路由key。
- pub/sub模式声明交换机名称和指定类型为fanout(广播模式,每个域交换机绑定的队列都会收到消息),无路由key,消费者端可定义匿名队列与声明交换机绑定后就可以获取到生产者端发出的消息。
- rounting路由模式交换机类型为:direct,队列绑定交换机的时候需要指定routing key,消费者端可根据对应的路由key来获取对应路由的消息。
- topic模式交换机对应类型为topic,跟路由模式相同,只不过
Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符
- 这三个模式重点就在于交换机和对应的路由key。
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.0.0</version> </dependency>
package com.rabbitmq.controller; import com.rabbitmq.client.*;public class SendController { private final static String QUEUE_NAME = "hello"; private static final String TASK_QUEUE_NAME = "task_queue"; private static final String EXCHANGE_NAME = "logs"; private static final String EXCHANGE_NAME2 = "direct_logs"; public static void main(String[] argv) throws Exception { demo(); } //注释版本 public static void demo() throws Exception { //创建链接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置RabbitMQ服务主机地址,默认localhost connectionFactory.setHost("localhost"); //设置RabbitMQ服务端口,默认5672 connectionFactory.setPort(5672); //设置虚拟主机名字(在rabbitmq服务器中,消息队列是放在虚拟主机中的,这是为了更好分类管理各种消息队列,一般会加/) //虚拟主机名字得先在服务器中手动添加一个,否则会找不到虚拟主机 connectionFactory.setVirtualHost("demo"); //设置用户连接名,默认guest(你想用哪个rabbitmq服务器中的账户就填哪个的账号密码) connectionFactory.setUsername("guest"); //设置链接密码,默认guest connectionFactory.setPassword("guest"); //创建链接(在本身和rabbitmq服务器之间建立连接,类似跟redis、mysql之间建立连接) Connection connection = connectionFactory.newConnection(); //创建频道(在本身和指定的rabbitmq服务器中的指定虚拟主机之间建立稳定、快速的通道) Channel channel = connection.createChannel(); //声明队列(说明要在rabbitmq服务器中指定的虚拟主机中的哪条消息队列) /** * 声明队列 * 参数1:队列名称 * 参数2:是否定义持久化队列 * 参数3:是否独占本次连接(其它连接是否能连接到本条队列) * 参数4:是否在不使用的时候自动删除队列 * 参数5:队列其它参数 * **/ channel.queueDeclare(QUEUE_NAME,false,false,false,null); //创建消息 String message = "hello!qianyi!"; //消息发送 /** * 消息发送 * 参数1:交换机名称,如果没有指定则使用默认Default Exchage(不写就填空串) * 参数2:路由key,简单模式可以传递队列名称 * 参数3:消息其它属性(没有填null) * 参数4:消息内容(消息内容是字符串,需要转换成字节数组才能传输) */ channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); //关闭资源(连接和频道的) channel.close(); connection.close(); } //Worker模式 public static void workerPattern() throws Exception{ ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); String message = "tasequeue"; // channel.basicQos(1); for(int i=0;i<20;i++){ channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, (message+i).getBytes("UTF-8")); } System.out.println(" [x] Sent '" + message + "'"); } } //Pub/Sub模式 先启动监听 后发送消息直接获取 public static void pubPattern() throws Exception{ ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { /** * 声明交换机 * 参数1:交换机名称 * 参数2:交换机类型,fanout、topic、direct、headers(以下用fanout类型,广播模式,每个与交换机绑定的队列都会接收到信息) */ channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT); String message = "info: Hello World!"; channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8")); System.out.println(" [x] Sent '" + message + "'"); } } //路由模式 根据不同路由发送不同消息 public static void routePattern() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { //交换机 channel.exchangeDeclare(EXCHANGE_NAME2, BuiltinExchangeType.DIRECT); //指定的可以 String severity = "info"; //消息内容 String message = "directMsg"; String errormessage = "errorMsg"; channel.basicPublish(EXCHANGE_NAME2, severity, null, message.getBytes("UTF-8")); channel.basicPublish(EXCHANGE_NAME2, "error", null, errormessage.getBytes("UTF-8")); System.out.println(" [x] Sent '" + severity + "':'" + message + "'"); } } }
消费者:
package com.rabbitmq.receiver; import com.rabbitmq.client.*; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.concurrent.TimeUnit; @Component public class Receiver { private final static String QUEUE_NAME = "hello"; private static final String TASK_QUEUE_NAME = "task_queue"; private static final String EXCHANGE_NAME = "logs"; private static final String EXCHANGE_NAME2 = "direct_logs"; public static void main(String[] argv) throws Exception { demo(); } //普通 public static void demo() throws Exception { //创建链接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置RabbitMQ服务主机地址,默认localhost connectionFactory.setHost("localhost"); //设置RabbitMQ服务端口,默认5672 connectionFactory.setPort(5672); //设置虚拟主机名字(rabbitmq服务器上创建的虚拟主机) connectionFactory.setVirtualHost("demo"); //设置用户连接名,默认guest(指定用哪个用户登录,guest是默认超级管理员) connectionFactory.setUsername("guest"); //设置链接密码,默认guest connectionFactory.setPassword("guest"); //创建链接(连接到rabbitmq服务器) Connection connection = connectionFactory.newConnection(); //创建频道(创建连接rabbitmq服务器之间的稳定、高效的频道,持久通讯) Channel channel = connection.createChannel(); //申明队列,指定到哪个队列获取消息 /** * 声明队列 * 参数1:队列名称 * 参数2:是否定义持久化队列 * 参数3:是否独占本次连接(其它连接是否能连接到本条队列) * 参数4:是否在不使用的时候自动删除队列 * 参数5:队列其它参数 * **/ channel.queueDeclare(QUEUE_NAME,false,false,false,null); //创建消费者,并设置消息处理(DefaultConsumer:消息消费者,参数传入创建的频道)然后再重写handleDelivery方法,可以用lambab表达式 DefaultConsumer defaultConsumer = new DefaultConsumer(channel){ /*** * @param consumerTag 消息者标签,在channel.basicConsume时候可以指定 * @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送) * @param properties 属性信息 * @param body 消息 * @throws IOException */ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { //路由的key String routingKey = envelope.getRoutingKey(); //获取交换机信息 String exchange = envelope.getExchange(); //获取消息ID long deliveryTag = envelope.getDeliveryTag(); //获取消息信息 String message = new String(body, "UTF-8"); //输出获得的消息内容 System.out.println("routingKey:"+routingKey+",exchange:"+exchange+",deliveryTag:"+deliveryTag+",message:"+message); } }; //消息监听 /** * 消息监听 * 要监听哪个队列?当消费者收到消息之后是否自动告诉rebbitmq服务器已经收到?收到消息之后,如何处理呢? * 参数1:队列名称 * 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认 * 参数3:消息接收到后回调(传入上面创建的消费者对象,这个消费者对象中对做了对收到的消息处理) */ channel.basicConsume(QUEUE_NAME,true,defaultConsumer); //关闭资源(不建议关闭,建议一直监听消息) //channel.close(); //connection.close(); } //worker模式 public static void workerPattern() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); final Connection connection = factory.newConnection(); final Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); //可以把预取的初始数量设置为1,这样,性能较高的消费者就会去消费更过的消息,大大提高效率 //channel.basicQos(1); DeliverCallback deliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }finally { //channel.basicAck()方法在消息消费完成时通知服务端消费已经完成。 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } System.out.println(" [x] Received '" + message + "'"); }; //手动确认模式 消费者从队列中获取消息之后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。 //手动设置需要使用 channel.basicAck() 方法来通知服务端消费已完成 channel.basicConsume(TASK_QUEUE_NAME, false, deliverCallback, consumerTag -> { }); } //Pub/Sub模式 public static void pubPattern() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); /** * 声明交换机 * 参数1:交换机名称 * 参数2:交换机类型,fanout、topic、direct、headers(以下用fanout类型,广播模式,每个与交换机绑定的队列都会接收到信息) */ channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); //定义一个匿名额队列 String queueName = channel.queueDeclare().getQueue(); //将队列和交换机绑定 channel.queueBind(queueName, EXCHANGE_NAME, ""); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"+queueName); DeliverCallback deliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [x] Received '" + message + "'"); }; channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { }); } //路由模式 public static void routePattern() throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME2, BuiltinExchangeType.DIRECT); String queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, EXCHANGE_NAME2, "info"); channel.queueBind(queueName, EXCHANGE_NAME2, "error"); channel.queueBind(queueName, EXCHANGE_NAME2, "warning"); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"+queueName); DeliverCallback deliverCallback = (consumerTag, delivery) -> { String message = new String(delivery.getBody(), "UTF-8"); System.out.println(" [x] Received '" + delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); }; channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { }); } }
上面的都是一些简单的例子,就是本地运行生产者发送消息 然后本地运行消费者接收消息,这里面有个关于持久化的东西要注意下 这是后面整合springboot时才发现的一个问题
rebbitMQ的持久化其实是有三种:交换机持久化、队列持久化和消息持久化 持久化之后 对应的数据即使重启消息服务也不会丢失
交换机声明持久化:channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true); 重点第三个参数 durable 设置为true
队列声明持久化: channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); 重点是第二个参数 durable 设置为true
消息发送持久化: channel.basicPublish("", TASK_QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,(message+i).getBytes("UTF-8")); 重点是 MessageProperties.PERSISTENT_TEXT_PLAIN 这个参数也就是basicPublish的第三个参数
这三个持久化 除了消息持久化有点不一样 交换机和队列声明持久化的参数名是相同的 设置为true就行了
在springboot整合中 rabbitTemplate.convertAndSend这个方法发送消息是默认持久化的 也就是说为了防止消息丢失只需要将交换机和队列声明持久化就可以了
springboot整合:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
生产者:
package com.rabbitmq.controller; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/demo") public class QueueController { @Autowired private RabbitTemplate rabbitTemplate; @RequestMapping("/queue") @ResponseBody public String sendMsg(String msg){ rabbitTemplate.convertAndSend("","boot_queue",msg); return "发送成功"; } @RequestMapping("/worker") @ResponseBody public String sendMsg(){ for(int i= 0;i<20;i++){ rabbitTemplate.convertAndSend("","boot_worker","msg:"+i); } return "发送成功"; } //对应所有消息都能接受到 广播消息 @RequestMapping("/pubsub") @ResponseBody public String pubsubMsg(String msg){ rabbitTemplate.convertAndSend("boot_pubsub","","广播消息"); return "发送成功"; } @RequestMapping("/rounting") @ResponseBody public String rountingMsg(){ rabbitTemplate.convertAndSend("boot_rounting_exchange","info","rountinginfo消息"); rabbitTemplate.convertAndSend("boot_rounting_exchange","error","rountingerror消息"); return "发送成功"; } @RequestMapping("/topic") @ResponseBody public String topicMsg(String key){ rabbitTemplate.convertAndSend("boot_topic_exchange",key,"topic消息"); return "发送成功"; } }
消费者:
package com.rabbitmq.receiver; import com.rabbitmq.client.Channel; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class QueueListener { @RabbitListener(queuesToDeclare = @Queue("boot_queue")) public void receiveMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel){ System.out.println("收到消息:"+msg); } @RabbitListener(queuesToDeclare = @Queue("boot_worker")) public void receiveWorkMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, Channel channel) throws IOException { System.out.println("工作者模式:"+msg); // multiple true 返回消息确认该消息已被消费 false 不返回直接消费 channel.basicAck(deliveryTag,false); } //Pub/Sub模式 @RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(name = "boot_pubsub",type = "fanout"))) public void pubsubMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, Channel channel) throws Exception { System.out.println("收到消息1:"+msg); channel.basicAck(deliveryTag,false); } //Routing模式 接收对应key里面的数据 @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "boot_rounting_queue01"), exchange = @Exchange(name = "boot_rounting_exchange",type = "direct"),key = {"info"} )) public void rountingInfoMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, Channel channel) throws Exception { System.out.println("error&info 收到消息:"+msg); channel.basicAck(deliveryTag,false); } //Routing模式 接收对应key里面的数据 @RabbitListener(bindings = @QueueBinding(value = @Queue(name = "boot_rounting_queue01"), exchange = @Exchange(name = "boot_rounting_exchange",type = "direct"),key = {"error"} )) public void rountingErrorMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, Channel channel) throws Exception { System.out.println("error&info 收到消息:"+msg); channel.basicAck(deliveryTag,false); } //Topic模式 接受可匹配通配符 @RabbitListener(bindings = @QueueBinding(value = @Queue("boot_topic_queue01"), exchange = @Exchange(name = "boot_topic_exchange",type = "topic"),key = "order.*" )) public void topicMsg(String msg, @Header(AmqpHeaders.DELIVERY_TAG)long deliveryTag, Channel channel) throws Exception { System.out.println("topic收取消息:"+msg); channel.basicAck(deliveryTag,false); } }