RabbitMQ学习笔记二
RabbitMQ消息如何保障100%的投递成功
什么是生产端的可靠性投递?
- 保障消息的成功发送
- 保证RabbitMQ节点的成功接受
- 发送端收到MQ节点(Broker)确认应答
- 完善的消息进行补偿机制
解决方案
- 消息落库,对消息进行状态打标(在发送消息时,消息持久化到数据库,并设计一个消息发送状态)。
在高并发的场景下,上面的方案是否合适?
- 消息的延迟投递,做二次确认,回调检查
幂等行概念
- 就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用
在海量订单产生的业务高峰期,如何避免消息的重复消费问题?(消费端-幂等性应用)
主流的幂等性操作方案:
- 唯一id+指纹码机制,利用数据库主键去重
- 好处: 实现简单
- 缺点: 高并发下数据库写入的性能瓶颈
- 解决方案:跟ID进行分裤分表进行算法路由。
- 利用Redis的原子性去实现
- 考虑的问题:
- 是否需要进行落库,如果落库,关键解决问题是数据库和缓冲如何做到原子性?
- 如果不落库,那么存储到缓冲中,如何设置定时同步策略?
- 考虑的问题:
RabbitMQ投递消息机制
Confirm确认消息
Confirm消息确认机制:
消息的确认,指生产者投递消息后,如果broker收到消息,则会给生产者一个应答。生产者接受应答,用来确认消息是否正常发送到Broker,这种方式也是消息的可靠性投递的核心保障
如何实现Confirm确认消息?
- 在channel开启确认模式:
channel.confirmSelect()
- 在channel上添加监听:
addConfirmListener
, 监听成功和失败返回结果,根据具体的结果对消息进行重新发送、记录日志等后续操作。
Return返回消息
Return消息机制:
- Return Listener用于处理一些不可路由的消息。
- 消息生产者,指定一个Exchange和RoutingKey,把消息送达到某一队列中去,然后消费者监听队列,进行消费处理操作。
- 在某些情况下,若在发送消息的时候,当前exchange不存在或者路由key路由不到,这个时候需要监听这种不可达消息,就要使用Return Listener!
- 基础API关键配置项:
- Mandatory:如果为true,则监听器会接受到路由不可达的消息,然后进行处理,如果为false,那么broker则自动删除消息
自定义消费者
一般是在代码中编写while循环,然后进行consumer.nextDelivery
方法进行获取下一条消息,然后进行消费处理!
但是自定义Consumer更加的方便,解耦性更强,也是实际工作中最常用的方式。
消费端自定义监听的实现
public class MyConsumer extends DefaultConsumer {
public MyConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(string consumerTag, Envelop envelope, BasicPropeties properties, byte[] body) throws IOException {
// todo
}
消息的限流
假设一个场景,若RabbitMQ服务器由上万条未处理的消息,随便打开一个消费者客户端,会出现下面情况:
- 巨量的消息全部推送过来,单个客户端无法同时处理这么多数据,导致故障
- RabbitMQ提供了一种qos(服务质量保证)功能,即在非自动确认消息的前提下,如果一定数目的消息(通过基于consumer或者channle设置的Qos值)未被确认前,不进行消费新的消息。
void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);
- prefetchSize消息限制大小,0则不做限制;
- prefetchCount一次最多处理多少条消息;会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,一旦有N个消息还没ack,则consumer将block(阻塞),直到有消息ack。
- global线程策略在什么级别应用,true-channel级别,false-consumer级别;一般设置false;
- 在no_ask=false的情况下生效,即在自动应答的情况下,prefetchSize和global不生效,即手动签收
消息的ACK与重回队列
消费端的手工Ack和NAck
- 消费端进行消费时,由于业务异常可以进行日志记录,然后进行补偿
- 由于服务器宕机等严重问题,就需要手工进行ack保障消费端消费成功
消费端的重回队列
- 消费端重回队列是为了对没有处理成功的消息,把消息重新投递给broker
- 一般实际应用中,都会关闭重回队列,也就是设置为false
TTL队列/消息
TTL
- TTL是time to live的缩写,即生存时间
- RabbitMQ支持消息的过期时间,在消息发送时可以指定
- RabbitMQ支持队列的过期时间,从消息入队列开始计算,只要超过队列的超时时间配置,那么消息自动清除
死信队列: DLX,Deal-Letter-Exchange
- 利用DLX,当消息在一个队列中变成死信(dead message)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX。
消息变成死信有以下几种情况
- 消息被拒绝(basic.reject/basic.nack)并且requeue=false
- 消息ttl过球
- 队列达到最大长度
死信队列
- DLX是一个正常的Exchange,和一般的Exchange没有区别,能在任何的队列上被指定,实际就是设置某个队列的属性
- 当这个队列有死信时,RabbitMQ就会自动将这个消息重新发不到设置的Exchange上去,进而被路由到另一个队列
- 可以监听这个队列中消息做相应的处理,这个特性可以弥补RabbitMQ3.0以前支持的immediate参数的功能。
死信队列设置
- 首先需要设置死信队列的exchange和queue,然后进行绑定
- Exchange: dlx.exchange
- Queue: dlx.queue
- RoutingKey: #
- 然后可进行正常声明交换机、队列、绑定;只不过需要在队列上加伤一个参数
arguments.put("x-dead-letter-exchange", "dlx.exchange");
- 这样消息在过期,requeue、队列达到最大长度时,消息就可以直接路由到死信队列。