RabbitMQ可靠性
消息队列如何解决消息丢失问题?
-
持久化
消息持久化
设置deliveryMode为PERSISITENT
队列持久化
设置durable为true
交换机持久化
设置durable为true
在SpringAMQP里面 消息持久化 ,交换机持久化,队列持久化都是默认开启的 ![](https://img-blog.csdnimg.cn/img_convert/83d220f96ad57391dcf9a2069421e2cc.png) 发布确认机制 确保消息发送到交换机
spring.rabbitmq.publisher-confirm-type=CORRELATED
消息回调机制 消息无法路由到指定的队列时会被回调
spring.rabbitmq.publisher-returns=true
应答机制: 设置自动应答模式为手动应答模式
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
重试机制:
自动ACK+重试
开启自动ACK,才会在到达最大重试上限后发送到死信队列,而且在重试过程中会独占当前线程,如果是单线程的消费者会导致其他消息阻塞,直至重试完成,所以可以使用
#重试机制
#开启消费者(程序出现异常的情况下会,捕获异常重试将不生效)进行重试
spring.rabbitmq.listener.simple.retry.enabled=true
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#最大间隔时间
spring.rabbitmq.listener.simple.retry.max-interval=20000ms
#重试间隔时间 3秒
spring.rabbitmq.listener.simple.retry.initial-interval=3000ms
#乘子 重试间隔*乘子得出下次重试间隔 3s 6s 12s 24s 此处24s>20s 走20s
spring.rabbitmq.listener.simple.retry.multiplier=2
#重试次数超过上面的设置之后是否丢弃(false不丢弃时需要写相应代码将该消息加入死信队列)
spring.rabbitmq.listener.simple.default-requeue-rejected=false
手动ack+重试
如果是手动ACK配置了重试机制,在抛出异常的时候仍会触发重试,但是达到重试上限之后,会永远处于Unacked状态,不会进入到死信队列,必须要手动拒绝才可以进入死信队列,所以说这里不用配置重试机制而是采用手动重试的方式
RabbitMQ 处理unacked消息
原因:消费端由于没有确认消息,导致队列阻塞,这是RabbitMQ的一种保护机制。防止当消息激增的时候,海量的消息进入consumer而引发consumer宕机。
RabbitMQ提供了一种QOS(服务质量保证)功能,即在开启手动确认消息的前提下,限制信道上的消费者所能保持的最大未确认的数量。可以通过设置PrefetchCount实现。
举例说明:可以理解为在consumer前面加了一个缓冲容器,容器能容纳最大的消息数量就是PrefetchCount。如果容器没有满RabbitMQ就会将消息投递到容器内,如果满了就不投递了。当consumer对消息进行ack以后就会将此消息移除,从而放入新的消息。
// 最小并发数
container.setConcurrentConsumers(1);
// 最大并发数
container.setMaxConcurrentConsumers(5);
// 削峰限流:消费端限流,一次处理一条数据。
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息
//一次处理的消息数量
container.setPrefetchCount(2);
//参数为0,轮询发送处理的消息,一次一条
container.setPrefetchCount(0);
判断队列的阻塞风险
当ack模式为manual,并且线上出现了unacked消息,这个时候不用慌。由于QOS是限制信道channel上的消费者所能保持的最大未确认的数量。所以允许出现unacked的数量可以通过channelCount * prefetchCount * 节点数量 得出。
channlCount就是由concurrency,max-concurrency决定的。
min = concurrency * prefetch * 节点数量
max = max-concurrency * prefetch * 节点数量
由此可以的出结论
unacked_msg_count < min 队列不会阻塞。但需要及时处理unacked的消息。
unacked_msg_count >= min 可能会出现堵塞。
unacked_msg_count >= max 队列一定阻塞。
消息的顺序性
一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。
产生多个consumer去消费一个queue,极有可能是因为:消息消费太慢,所以盲目让多个consumer同时来消费,而忽略了消息消费顺序性。
在某些情况下,消息是需要保证顺序性的,如果上图中的data1, data2, data3 分别意味着对某条数据的增改删,但是如果乱序以后就变成了:删改增。
解决方案:
1、拆分多个 queue,每个 queue 一个 consumer。
2、一个 queue,但是对应一个 consumer,然后这个 consumer 内部用内存队列(其实就是List而已)做排队,然后分发给底层不同的thread来处理(此方案可以支持高并发)。
实际consumer的数量是受限的,不会仅仅因为消息消费太慢而去增加consumer实例的数量,所以通过方案2的方式,可以在不增加consumer实例数量的前提下,加快消息消费的速度。
RabbitMQ幂等性
RabbitMQ如何避免重复消费?
-
使用Redis进行唯一特征存储,通常是将消息Id当作唯一特征进行存储
-
使用一个带唯一索引字段的表