文章目录
1 生产端如何可靠地投递消息
1.1 消息落库打标
消息落库,操作消息的状态位,然后利用定时任务轮询发送失败的消息并重试,但是不适合高并发场景。
- 第一步:生产者将消息写入数据库。
- 第二步:生产者向MQ发送消息。
- 第三步:生产者监听消息发送状态(confirm)。
- 第四步:生产者更新消息的状态位。
- 第五步:启动一个分布式定时任务轮询发送失败的消息,并重新发送。
1.2 消息延迟投递,通过二次确认回调检查。
- 第一步:生产者将消息写入数据库。
- 第二步:生产者第一次向MQ发送消息。
- 第三步:生产者第二次延迟向MQ发送消息(另一个队列)。
- 第四步:消费者消费并生成一条新的确认消息,发送往MQ。
- 第五步:另一个异步服务监听消费者的确认消息。
- 第六步:异步服务将消息写入数据库。
- 第七步:异步服务在另一个队列中收到了生产者发送的延迟消息。然后查询数据库确认是否成功处理,如果数据库中没有记录,就向生产者发送一个重新发送请求,重新发送该消息。
2 生产者确认(Confirm消息确认机制)
生产者投递消息后,如果Broker收到消息,就会给生产者一个应答,生产者接收到应答后就可以确认这条消息已正常发送到MQ中。
3 消息的幂等性保障
3.1 唯一ID + 指纹码 机制
根据唯一ID + 指纹码去数据库中查询是否有对应的记录,如果有则不处理消息到的消息。
好处:简单易实现,坏处:频繁操作数据库,有性能瓶颈。
解决方案:利用ID进行分库分表策略,进行算法路由,分摊数据库压力。
3.2 利用Redis的原子性实现
这个实现很简单,但是需要考虑以下两个问题:
- 如果数据落库的话,需要解决数据库和缓存之间的原子性问题。
- 如果数据不落库,应该设置怎样的定时同步策略。
4 Return消息机制
ReturnListener用于监听一些不可路由的消息,比如exchange不存在或者制定的routingkey路由不到。
主要涉及的配置项为mandatory:
- 为true,则生产者可以通过ReturnListener监听到,然后进行后续的处理。
- 为false,则直接丢弃消息。
与此同时,如果交换机将消息路由到队列时,发现队列上没有任何消费者,也会根据参数immediate做处理,不过该参数已经废弃了。
5 消息的分发与消费端限流
5.1 消息的分发
队列是以轮询的方式将消息分发给消费者。比如现在有n个消费者,会将第m条消息分发给m % n个消费者。
5.2 消费端的限流
通过channel.basicQos(int prefetchSize, int prefetchCount, boolean global)方法实现:
- prefetchSize用于限制消费者所能接收未确认消息的数量。一般默认为0,表示无限制。
- prefetchCount用于限制信道上的消费者所能保持的最大未确认消息的数量。
- global,默认为false,如果为true,则信道上所有的消费者都需要受到prefetchCount的限制,如果为false,则信道上新的消费者需要受到prefetchCount的限制。
注意: - autoAck设置为false。
- 对拉模式无效。
6 消息的TTL
TTL是Time to Live的缩写,也就是生存时间,单位毫秒。
- 在channel.queueDeclare方法中加入x-message-ttl参数,可以设置队列级别的过期时间。
- 在channel.basicPublish方法中加入expiration属性,可以设置消息级别的过期时间。
- 如果二者一起时间,则以消息的TTL二者之间较小的那个数值为准。
- 推荐设置队列级别的过期时间,因为队列级别的过期消息处理只需要定期从队列头部开始扫描即可,而消息级别的过期消息处理需要扫描整个队列,性能低。
7 队列的TTL
- 通过channel.queueDeclare方法中的x-expires参数设置队列被删除前空闲的时间。单位毫秒。
- 如果RabbitMQ重启,持久化的队列的过期时间会被重新计算。