RabbitMQ相关
RabbitMQ的AMQP协议是什么
AMQP(Advanced Message Queuing Protocol),高级消息队列协议,提供统一消息服务的开放标准,其核心目标是实现客户端与消息服务之前的高效、安全异步通信,并且在传递的时候不受客户端和开发语言的限制。
RabbitMQ消息的工作模式
简单模式:一个生产者一个队列一个消费者,先进先出,一般都是自己实验的时候玩。
## 队列配置 @Component public class QueueConfig { @Bean public Queue queueTest1(){ //name:队列名称 //durable:持久化标志,true表示队列会被持久化存储(重启后不丢失),false表示队列是临时的(重启后消失)。默认值为true。 return new Queue("queue_test1", true); } } ##生产者 @Slf4j @RestController @RequestMapping("/queue") public class QueueProductController { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("/queueToClient") public String queueToClient(){ Map<String, String> map1 = new HashMap<>(); map1.put("测试简单模式","11111"); rabbitTemplate.convertAndSend("queue_test1",map1); return "简单模式消息发送成功"; } } ##消费者 @Component public class QueueClient { @RabbitListener(queues = "queue_test1") @RabbitHandler public void topicQuery1(HashMap<String,String> jsonMap){ System.out.println("我是简单模式消费者"+jsonMap); } }
队列模式:一个生产者一个队列多个消费者,消费者之间竞争消息,实现了消费者的均衡,一个消息只能被一个消费者消费。
交换机模式:
直连交换机(Direct Exchange):根据指定的路由键完全匹配路由到队列。若路由键不匹配,消息不会被分发。例如,若队列绑定键为 "dog",则仅匹配路由键为 "dog" 的消息。
@Component public class DirectConfig { @Bean public DirectExchange directExchangeOne(){ // name:交换机名称,用于标识该交换机在RabbitMQ中的唯一性。 // durable:持久化标志,true表示交换机会被持久化存储(重启后不丢失),false表示临时交换机(重启后消失)。默认值为true。 // autoDelete:自动删除标志,true表示当所有绑定的队列解绑后自动删除该交换机,false表示手动删除 return new DirectExchange("directExchangeOne",true,false); } @Bean public Queue directQueueOne(){ //name:交换机名称,用于标识该交换机在RabbitMQ中的唯一性。 //durable:持久化标志,true表示交换机会被持久化存储(重启后不丢失),false表示临时交换机(重启后消失)。默认值为true。 return new Queue("directQueueOne", true); } @Bean public Queue directQueueTwo(){ return new Queue("directQueueTwo"); } @Bean public Binding directBindingOne(){ return BindingBuilder.bind(directQueueOne()).to(directExchangeOne()).with("directKey1"); } @Bean public Binding directBindingTwo(){ return BindingBuilder.bind(directQueueTwo()).to(directExchangeOne()).with("directKey2"); } }
扇型交换机(Fanout Exchange):将消息广播到所有绑定的队列,不依赖路由键。适用于需要消息被多个队列接收的场景。
@Component public class FanoutConfig { @Bean public FanoutExchange fanoutExchange1(){ return new FanoutExchange("fanoutExchange1"); } @Bean public Queue fanoutQuery1(){ return new Queue("fanout_queue1"); } @Bean public Queue fanoutQuery2(){ return new Queue("fanout_queue2"); } @Bean public Binding fanoutBinding1(){ return BindingBuilder.bind(fanoutQuery1()).to(fanoutExchange1()); } @Bean public Binding fanoutBinding2(){ return BindingBuilder.bind(fanoutQuery2()).to(fanoutExchange1()); } }
主题交换机(Topic Exchange):支持路由键使用通配符 *(匹配单个词)和 #(匹配多个词)。例如,绑定模式为 audit.# 可接收 audit.irs.corporate 类型的消息。
@Component public class TopicConfig { @Bean public TopicExchange topicExchange(){ return new TopicExchange("topicExchange1"); } @Bean public Queue topicQueue1(){ return new Queue("topicQueue1"); } @Bean public Queue topicQueue2(){ return new Queue("topicQueue2"); } @Bean public Binding topicBinding1(){ return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("hello.world"); } @Bean public Binding topicBinding2(){ return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("hello.#"); } }
MQ如何保证消息不丢失
消息丢失分为五个场景

1.生产者到队列过程中
2.交换机到队列的过程
3.队列到消费者的过程
4.交换机未持久化
5.队列未持久化
消息丢失解决办法
application.properties相关配置
spring.application.name=demo ## MQ spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest ## 开启生成者消息到交换机回调 spring.rabbitmq.publisher-confirm-type=correlated ## 设置消息强制路由模式,确保消息无法投递到队列时返回给生产者,避免消息丢失 true:回调 false:丢弃 spring.rabbitmq.template.mandatory=true ## 开启交换机发送消息到队列中失败回调 spring.rabbitmq.publisher-returns=true ## 开启消费者手动确认机制 spring.rabbitmq.listener.simple.acknowledge-mode=manual ## 消费失败开启重试 最大重试三次、每次1000ms,时间每次成2,最大时间间隔5000ms spring.rabbitmq.listener.direct.retry.enabled=true spring.rabbitmq.listener.direct.retry.max-attempts=3 spring.rabbitmq.listener.direct.retry.initial-interval=1000 spring.rabbitmq.listener.direct.retry.multiplier=2 spring.rabbitmq.listener.direct.retry.max-interval=5000 ## 日志 logging.level.com.sunny= info
1.生产者到队列过程中防止消息丢失
防止生产者到交换机过程消息丢失可以开启publisher-confirm属性,然后在配置RabbitTemplate的时候监听成功失败的回调
## 开启生成者消息到交换机回调 spring.rabbitmq.publisher-confirm-type=correlated @Configuration @Slf4j public class RabbitMQConfig { @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setMessageConverter(jsonMessageConverter()); // 使用Jackson进行消息转换 rabbitTemplate.setReplyTimeout(10000); // 设置回复超时时间 //开启消息发送到交换机回调 rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { if(ack){ log.info("消息发送到交换机成功,相关消息:{}",correlationData); }else { log.info("消息发送到交换机失败,相关消息{},失败原因:{}",correlationData,cause); } } }); //开启交换机发送消息到队列中失败回调 // rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { // @Override // public void returnedMessage(ReturnedMessage returnedMessage) { // log.info("消息退回: {}, 状态码: {}, 原因: {}, 交换机: {}, 路由键: {}", // returnedMessage.getMessage(), // returnedMessage.getReplyCode(), // returnedMessage.getReplyText(), // returnedMessage.getExchange(), // returnedMessage.getRoutingKey()); // } // }); return rabbitTemplate; } @Bean public MessageConverter jsonMessageConverter() { return new Jackson2JsonMessageConverter(); } }
2.交换机到队列的过程防止消息丢失
防止交换机到队列的过程消息丢失可以开启mandatory和publisher-returns属性,然后在配置RabbitTemplate的时候监听失败的回调
## 设置消息强制路由模式,确保消息无法投递到队列时返回给生产者,避免消息丢失 true:回调 false:丢弃 spring.rabbitmq.template.mandatory=true ## 开启交换机发送消息到队列中失败回调 spring.rabbitmq.publisher-returns=true @Configuration @Slf4j public class RabbitMQConfig { @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setMessageConverter(jsonMessageConverter()); // 使用Jackson进行消息转换 rabbitTemplate.setReplyTimeout(10000); // 设置回复超时时间 //开启消息发送到交换机回调 // rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { // @Override // public void confirm(CorrelationData correlationData, boolean ack, String cause) { // if(ack){ // log.info("消息发送到交换机成功,相关消息:{}",correlationData); // }else { // log.info("消息发送到交换机失败,相关消息{},失败原因:{}",correlationData,cause); // } // } // }); //开启交换机发送消息到队列中失败回调 rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { log.info("消息退回: {}, 状态码: {}, 原因: {}, 交换机: {}, 路由键: {}", returnedMessage.getMessage(), returnedMessage.getReplyCode(), returnedMessage.getReplyText(), returnedMessage.getExchange(), returnedMessage.getRoutingKey()); } }); return rabbitTemplate; } @Bean public MessageConverter jsonMessageConverter() { return new Jackson2JsonMessageConverter(); } }
3.队列到消费者的过程防止消息丢失
防止队列到消费者的过程防止消息丢失可以开启acknowledge-mode属性,让消费确认机制改为手动确认模式, 默认情况下自动确认
## 开启消费者手动确认机制 spring.rabbitmq.listener.simple.acknowledge-mode=manual @Component @Slf4j public class DirectClient { @RabbitListener(queues = "directQueueOne") @RabbitHandler public void directClientOne(Message message, Channel channel, @Payload HashMap<String,String> map){ log.info("消费者消费消息message:{},channel:{},map:{}",message,channel,map); try { if(CollectionUtils.isEmpty(map) || map.get("name").equals("error")){ log.info("消费者消费业务异常"); //deliveryTag:消息的唯一标识符(单调递增),用于指定需要拒绝的消息。 //multiple:布尔值,表示是否批量拒绝消息。若为true,则拒绝当前消息及之前所有未确认的消息;若为false,仅拒绝当前消息。 //requeue:布尔值,决定被拒绝的消息是否重新入队。若为true,消息会重新放回队列;若为false,消息将被丢弃或进入死信队列 channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true); return; } } catch (Exception e) { log.error("消费者消费消息NACK异常:",e); } try { log.info("消费者消费业务正常"); channel.basicAck(message.getMessageProperties().getDeliveryTag(),true); } catch (Exception e) { log.error("消费者消费消息ACK异常:",e); } } @RabbitListener(queues = "directQueueTwo") @RabbitHandler public void directClientTwo(@Payload HashMap<String,String>map){ System.out.println("直连队列消息2:" + map); } }
4.交换机持久化
在配置交换机时候直接设置交换机持久化durable=true
@Bean public DirectExchange directExchangeOne(){ // name:交换机名称,用于标识该交换机在RabbitMQ中的唯一性。 // durable:持久化标志,true表示交换机会被持久化存储(重启后不丢失),false表示临时交换机(重启后消失)。默认值为true。 // autoDelete:自动删除标志,true表示当所有绑定的队列解绑后自动删除该交换机,false表示手动删除 return new DirectExchange("directExchangeOne",true,false); }
5.队列持久化
在配置队列的时候直接设置队列持久化durable=true
@Bean public Queue directQueueOne(){ //name:队列名称 //durable:持久化标志,true表示队列持久化存储(重启后不丢失),false表示临时队列(重启后消失)。默认值为true。 return new Queue("directQueueOne", true); }
消息防重复
生产者在进行数据分布的时候透传唯一Id,订单Id,消息Id,UUID,消费端消费的时候判断唯一Id是否被消费过,消费过则直接过滤掉,没有消费则进行消费。在存唯一Id的时候要给过期时间,可以结合redis进行缓存。
RabbitMQ如何保证消息的顺序
1.一个生产者一个队列一个消费者。
2.一个消费者消费完成后才进行下一个生产。
3.加时间字段业务内部自己排序。
4.使用优先级队列,可以保证单个队列安装优先级消费。
5.使用延迟队列,优先级高的队列设置短的延迟时间。
延迟队列/死信队列
场景:
1.订单在十分钟之内未支付则自动取消
2.新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
3.用户注册成功后,如果三天内没有登陆则进行短信提醒。
4.用户发起退款,如果三天内没有得到处理则通知相关运营人员。
5.预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议
方式:
1.通过死信队列(DLX)实现
2.通过延迟插件 rabbitmq-delayed-message-exchange实现。
死信队列代码实现:
逻辑示意图

队列和交换机配置
@Component public class TtlDirectConfig { //普通交换directExchangeA 普通队列queueA 普通路由键routingA //死信交换机ttlDirectExchangeA 死信队列ttlQueueA 路由键ttlRoutingA @Bean public DirectExchange directExchangeA(){ return new DirectExchange("directExchangeA"); } @Bean public DirectExchange ttlDirectExchangeA(){ return new DirectExchange("ttlDirectExchangeA"); } //配置普通队列A并和延迟交换机绑定 @Bean public Queue queueA(){ Map<String, Object> args = new HashMap<>(3); //声明当前队列绑定的死信交换机 args.put("x-dead-letter-exchange", "ttlDirectExchangeA"); //声明当前队列的死信路由 key args.put("x-dead-letter-routing-key", "ttlRoutingA"); //声明队列的 TTL args.put("x-message-ttl", 10000); return QueueBuilder.durable("queueA").withArguments(args).build(); } @Bean public Queue ttlQueueA(){ return new Queue("ttlQueueA"); } @Bean public Binding directBindingA(){ return BindingBuilder.bind(queueA()).to(directExchangeA()).with("routingA"); } @Bean public Binding ttlDirectBindingA(){ return BindingBuilder.bind(ttlQueueA()).to(ttlDirectExchangeA()).with("ttlRoutingA"); } }
生产者配置
@RestController @RequestMapping("/ttlDirect") public class TtlDirectProductController { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("/ttlDirectToClient") public String ttlDirectToClient(){ log.info("当前时间:{},发送死信队列信息", new Date().toString()); rabbitTemplate.convertAndSend("directExchangeA","routingA","一条延迟10的消息"); return "直连队列消息发送成功"; } }
消费者配置
@Component @Slf4j public class TtlDirectConClient { @RabbitListener(queues = "ttlQueueA") @RabbitHandler public void directClientOne(Message message, Channel channel){ String msg = new String(message.getBody()); log.info("当前时间:{},收到死信队列信息{}", new Date().toString(), msg); } }
RabbitMQ可视化页面显示

执行结果

RabbitMQ如何保证高可用
rabbitMq集群有三种模式:
单机模式:
所有组件(生产者、消费者、队列、交换机)都运行在同一台机器上,配置和管理相对简单,但缺乏高可用性,一旦节点故障将导致服务不可用。
普通集群模式:

1.在多台机器上部署rabbitMq实例,但是创建的队列(queue)只会在一台机器实例上,其余机器保存队列的元数据(队列放在哪台broker上,是否持久化等),但不包含发送到队列中的消息。
2.如果消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
3.如何其中一条节点宕机了,会导致其他实例都无法拉取数据,只有等机器恢复了才能继续拉取数据。
4.所以普通集群模式并没有提供高可用,这种方案只是提高了吞吐量。
镜像集群模式:

1.在多台机器上部署rabbitMq实例,每台机器上都有这个队列的完整镜像,包括元数据和消息数据,每次写消息到队列的时候,都会自动把消息通过到多个实例的队列上。
2.这种模式的好处在于,任何一台机器宕机了,其他的机器还可以使用。
3.这种模式性能消耗太大,所有机器都要进行消息的同步,导致网络压力和消耗很大。 没有扩展性可言,如果有一个queue负载很重,就算加了机器,新增的机器还是包含了这个queue的所有数据,并没有办法扩展queue。

浙公网安备 33010602011771号