RabbitMQ笔记
本篇主要记录内容:docker安装rabbitmq、rabbitmq 交换机directExchange、fanoutExchange、topicExchange三种学习,还有其他的(header)用的不多就不记录了。消息确认、延迟、死信队列等。
1:docker安装rabbitmq
//获取镜像 1:docker pull rabbitmq //创建并启动容器 2:docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq
到此就rabbitmq环境就准备完成了,不得不说docker安装真是简单。。。
2:与springboot整合配置测试环境
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
添加配置
spring: rabbitmq: host: 127.0.0.1 port: 5672 username: admin password: admin virtual-host: /
3:directExchange(直连交换机,根据路由key进行分发)
代码中配置队列、也可以采用注解方式配置、也可在rabbit控制台操作(最好在代码中配置)
1:声明交换机
2:声明队列
3:创建绑定Key
4:bind队列与交换机
DirectConfig
package com.person.chenpt.config.rabbit; import com.person.chenpt.constans.RabbitConst; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 操作rabbitmq消息队列步骤 * 代码中配置、也可以采用注解方式配置、也可在rabbit控制台操作(最好在代码中配置) * 1:声明交换机 * 2:声明队列 * 3:创建绑定Key * 4:bind队列与交换机 * * * direct 直连交换机 根据路由key进行分发 多个队列binding同一个交换机 * 配置多台监听绑定到同一个直连交互的同一个队列 * 会以轮询的方式对消息进行消费,而且不存在重复消费。 * * * @Author: chenpt * @Description: * @Date: Created in 2022-07-28 11:08 * @Modified By: */ @Configuration public class DirectConfig { @Bean public DirectExchange directExchange(){ return new DirectExchange(RabbitConst.directExchange,true,false,null); } @Bean public Queue directQueue(){ /** * durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 * autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 */ return new Queue(RabbitConst.directQueue,true); } @Bean public Binding bindingDirectQueue(){ return BindingBuilder.bind(directQueue()).to(directExchange()).with(RabbitConst.directRoutingKey); } @Bean public Queue directQueue2(){ return new Queue(RabbitConst.directQueue2,true); } @Bean public Binding bindingDirectQueue2(){ return BindingBuilder.bind(directQueue2()).to(directExchange()).with(RabbitConst.directRoutingKey); } }
controller
@RestController @RequestMapping("/rabbit") @Api(tags = "消息队列") public class RabbitMqController { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("/send") @ApiOperation("direct队列发送") public Result sendMessage(){ rabbitTemplate.convertAndSend(RabbitConst.directExchange,RabbitConst.directRo utingKey,"hello test"); return Result.success(); } }
consumer
@RabbitListener(queues = RabbitConst.directQueue2) public void process2(String testMsg){ System.out.println("DirectReceiver2消费者收到消息 : " + testMsg); }
4:FanoutExchange(扇形交换机)
不需要路由键、多个队列binding到此交换机均能收到消息
package com.person.chenpt.config.rabbit; import com.person.chenpt.constans.RabbitConst; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * * 扇型交换机 * 不需要路由键 多个队列binding到交换机都能收到消息 * 与多个队列采用同一个路由键binding到direct交换机效果相同 * * * @Author: chenpt * @Description: * @Date: Created in 2022-07-28 14:54 * @Modified By: */ @Configuration public class FanoutConfig { @Bean FanoutExchange fanoutExchange(){ return new FanoutExchange(RabbitConst.fanoutExchange,true,false); } @Bean Queue fanoutQueue1(){ return new Queue(RabbitConst.fanoutQueue1,true); } @Bean Queue fanoutQueue2(){ return new Queue(RabbitConst.fanoutQueue2,true); } @Bean Binding fanoutBinding1(){ return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange()); } @Bean Binding fanoutBinding2(){ return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange()); } }
controller
@GetMapping("/sendFanout") @ApiOperation("fanout队列发送") public Result sendFanoutMessage(){ for(int i=1;i<=10;i++){ rabbitTemplate.convertAndSend(RabbitConst.fanoutExchange,null,"hello fanout test"); } return Result.success("ok"); }
consume
@RabbitListener(queues = RabbitConst.fanoutQueue1) public void fanoutQueue1(String testMsg){ System.out.println("fanoutReceiver1消费者收到消息 : " + testMsg); } @RabbitListener(queues = RabbitConst.fanoutQueue2) public void fanoutQueue2(String testMsg){ System.out.println("fanoutReceiver2消费者收到消息 : " + testMsg); }
5:TopicExchange(主题交换机)
对路由键进行模式匹配后投递
符号 # 表示一个或多个词,符号 * 表示一个词。
例如“abc.#”能够匹配到“abc.def.ghi”,但是“abc.*” 只会匹配到“abc.def”
package com.person.chenpt.config.rabbit; import com.person.chenpt.constans.RabbitConst; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 主题模式 * 主题交换机,对路由键进行模式匹配后进行投递, * 符号 # 表示一个或多个词, * 符号 * 表示一个词。 * 因此“abc.#”能够匹配到“abc.def.ghi”, * 但是“abc.*” 只会匹配到“abc.def” * @Author: chenpt * @Description: * @Date: Created in 2022-07-28 15:28 * @Modified By: */ @Configuration public class TopicConfig { @Bean TopicExchange topicExchange(){ return new TopicExchange(RabbitConst.topicExchange,true,false); } @Bean Queue topicQueue1(){ return new Queue(RabbitConst.topicQueue1,true); } @Bean Queue topicQueue2(){ return new Queue(RabbitConst.topicQueue2,true); } @Bean Binding bindingTopicQueue1(){ return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with(RabbitConst.topicRoutingKey1); } @Bean Binding bindingTopicQueue2(){ return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with(RabbitConst.topicRoutingKey2); } }
controller
@GetMapping("/sendTopic") @ApiOperation("topic队列发送") public Result sendTopicMessage(){ rabbitTemplate.convertAndSend(RabbitConst.topicExchange,"chenpt.order.test","hello order test"); rabbitTemplate.convertAndSend(RabbitConst.topicExchange,"chenpt.sms","hello sms test"); return Result.success("ok"); }
consume
@RabbitListener(queues = RabbitConst.topicQueue1) public void topicQueue1(String testMsg){ System.out.println("topicReceiver1消费者收到消息 : " + testMsg); } @RabbitListener(queues = RabbitConst.topicQueue2) public void topicQueue2(String testMsg){ System.out.println("topicReceiver2消费者收到消息 : " + testMsg); }
6:DeadExchange 死信交换机
当消息在队列中变成死信时,被重新发送到一个特殊的交换机中,同时绑定的队列就成为死信队列。
以下几种情况会导致消息变为死信:
消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false;
消息过期;
队列达到最大长度。
DeadConfig
此配置下声明了一个deadQueue,另外创建了一个测试队列和死信队列进行绑定
package com.person.chenpt.config.rabbit; import com.person.chenpt.constans.RabbitConst; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * 死信队列--Dead-Letter-Exchange,死信交换器 * 消息在一个队列中变成死信(Dead Letter)之后,被重新发送到一个特殊的交换器(DLX)中,同时,绑定DLX的队列就称为“死信队列”。 * 以下几种情况导致消息变为死信: * 消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false; * 消息过期; * 队列达到最大长度。 * @Author: chenpt * @Description: * @Date: Created in 2022-07-29 11:31 * @Modified By: */ @Configuration public class DeadConfig { @Bean FanoutExchange deadExchange(){ return new FanoutExchange(RabbitConst.deadExchange,true,false); } @Bean Queue deadQueue(){ return new Queue(RabbitConst.deadQueue,true); } @Bean Binding deadBinding(){ return BindingBuilder.bind(deadQueue()).to(deadExchange()); } @Bean DirectExchange directExchangeBindDead(){ return new DirectExchange("testDirectEx",true,false); } @Bean Queue directQueueBindDead(){ Map<String,Object> deadmap = new HashMap<>(); // 绑定该队列到私信交换机 deadmap.put("x-dead-letter-exchange",RabbitConst.deadExchange); // deadmap.put("x-dead-letter-routing-key",null);//由于deadExchange是fanout类型的所以不需要routing-key return new Queue("testDirectQue",true,false,false,deadmap); } @Bean Binding directBindDead(){ return BindingBuilder.bind(directQueueBindDead()).to(directExchangeBindDead()).with("test"); } }
controller
/** * 发送带有过期时间的消息 (10s) * 超时未消费的消息则进入死信队列 * @return */ @GetMapping("/sendDlx") @ApiOperation("dlx测试") public Result sendDlx(){ rabbitTemplate.convertAndSend("testDirectEx","test","hello deadEx test",message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); message.getMessageProperties().setExpiration("10000"); return message; }); return Result.success("ok"); }
consume和消息确认一起讲
开启手动确认--配置文件需要修改
spring:
rabbitmq: host: localhost port: 5672 username: admin password: admin virtual-host: / listener: #设置监听容器(Listener container)类型,如不设置,将会默认为SimpleRabbitListenerContainerFactory,且下面的direct手动确认配置不生效 type: direct direct: #开启手动确认 acknowledge-mode: manual #是否重试 retry: enabled: true
consume
@RabbitListener(queues = "testDirectQue") public void testDirectQue(String testMsg, Message message, Channel channel) throws IOException { long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { // int err = 1/0;//模拟异常 logger.info("testDirectQue消费者收到消息 : {},消费的主题消息来自 : {}",testMsg,message.getMessageProperties().getConsumerQueue()); /** * 第二个参数,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息 */ channel.basicAck(deliveryTag, true); } catch (Exception e) { logger.info("=====================消费者异常========================"); /** * message.getMessageProperties().getRedelivered() * false 表示第一次进队列 * true 表示重入队列 */ if (message.getMessageProperties().getRedelivered()) { logger.info("================消息已重复处理失败,拒绝再次接收======================" + testMsg); /** * 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列、 * true会重新放回队列,所以需要自己根据业务逻辑判断什么时候使用拒绝 */ channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); } else { logger.info("====================消息即将再次返回队列处理=========================" + testMsg); /** * requeue为是否重新回到队列,true重新入队 */ channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); } } }
上述消息确认是在消费者中,其实生产者也有消息确认,当发送到交换机、队列时都有相应的回调函数代码如下
package com.person.chenpt.config.rabbit; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.ReturnedMessage; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 生产者 * 消息确认 * 可以在回调函数根据需求做对应的扩展或者业务数据处理 * @Author: chenpt * @Description: * @Date: Created in 2022-07-28 16:00 * @Modified By: */ @Configuration public class RabbitProductConfirmConfig { @Bean public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){ RabbitTemplate rabbitTemplate = new RabbitTemplate(); rabbitTemplate.setConnectionFactory(connectionFactory); //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数 rabbitTemplate.setMandatory(true); rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("ConfirmCallback: "+"相关数据:"+correlationData); System.out.println("ConfirmCallback: "+"确认情况:"+ack); System.out.println("ConfirmCallback: "+"原因:"+cause); } }); rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returned) { System.out.println("ReturnCallback: "+"消息:"+returned.getMessage()); System.out.println("ReturnCallback: "+"回应码:"+returned.getReplyCode()); System.out.println("ReturnCallback: "+"回应信息:"+returned.getReplyText()); System.out.println("ReturnCallback: "+"交换机:"+returned.getExchange()); System.out.println("ReturnCallback: "+"路由键:"+returned.getRoutingKey()); } }); return rabbitTemplate; } }
先记录这些基本使用应该没有问了,后续有知识点再更新记录
作者:
不二尘
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。