本文是Edison学习《消息队列高手课》基础篇的学习总结,掌握消息队列的核心基础知识对我们开发后端微服务大有裨益。
1 MQ的适用场景
(2)流量控制
优点在于 能够根据下游的处理能力自动调节流量,达到“削峰填谷”的作用
缺点在于 增加了系统调用链环节,总体响应时间也会延长,同时也增加了系统的复杂度
另一种限流方式:令牌桶控制流量
令牌桶的基本原理:单位时间内只发放固定数量的令牌到令牌桶中,规定服务在处理请求之前必须先从令牌桶中拿出一个令牌,如果令牌桶中没有令牌,则拒绝请求。这样就可以保证在单位时间内,能处理的请求不会发放令牌的数量,起到流量控制的作用。

(3)服务解耦
可以实现各个系统应用之间的解耦,达到下游系统的增加或变化,上游服务不需要更改的效果。
-
引入MQ会带来延迟问题(需要考虑业务容忍度)
-
增加了系统的复杂度(多了一个中间件,也多了运维成本)
-
可能会产生数据的不一致问题(无法实现强一致性)
2 如何选择MQ?
-
消息的可靠传递:确保不丢失消息;
-
Cluster:支持集群,确保不会因为某个节点宕机导致服务不可用,当然也不能丢消息;
-
性能:具备足够好的性能,能够满足大多数场景的性能要求;
-
RabbitMQ
-
优点:最流行的MQ之一,支持灵活的路由配置(Exchange)、支持众多的客户端编程语言
-
问题:
-
对消息堆积的支持并不太好,当大量消息积压时,会导致RabbitMQ性能急剧下降
-
性能不够好,虽然每秒能够处理几万到十几万消息,但对部分性能要求很高的场景不太够用
-
Erlang非常小众,学习成本高,不太适合主流开发人员做扩展和二次开发
-
-
-
RocketMQ
-
优点:不错的性能、稳定性 和 可靠性,还在持续的成长,此外还有非常活跃的中文社区,易于做扩展和二次开发
-
性能:每秒大概能处理几十万条消息,高出RabbitMQ一个数量级
-
-
问题:国际上的流行程度 和 与周边生态系统的集成和兼容程度要略逊一筹
-
-
Kafka
-
优点:与周边生态系统的兼容性是最好的,尤其在大数据和流计算领域。大量地使用批量和异步设计思想,做到了超高的性能。
-
性能:每秒大概能处理几十万条消息,与RocketMQ没有量级上的差异
-
-
问题:同步收发消息的响应延迟比较高,不太适合在线业务场景。因为它是攒一波再一起处理的设计,对于每秒钟消息数量没有那么多的时候,时延会比较高。
-
3 各个MQ的消息模型
3.1 RabbitMQ
同一份消息如果要被多个消费者来消费,需要配置多个Exchange将消息发送到多个队列,每个队列中都存放一份完整的消息数据,可以为一个消费者提供消费服务。


4 MQ实现分布式事务
以一个订单提交的场景来看:
如果在第四步提交事务消息失败了,Kafka会直接抛出异常,让用户自行处理,比如可以在业务代码中反复重试,直到提交成功,或者删除之前创建的订单进行补偿。
RocketMQ的事务反查机制会通过定期反查事务状态,来补偿提交事务消息可能出现的通信失败。而在Kafka中,没有类似的机制。
5 如何确保消息可靠性
5.1 检测消息丢失的方法
解法:通过常见的 请求确认机制(ACK)来保证消息的可靠传递。
要点:在异步发送时,需要在回调方法里面进行检查,切不可忘记。
解法:和生产阶段类似,在执行完消费业务逻辑成功后再给Broker发送ACK确认。
要点:不要在收到消息后就立即发送ACK确认,手动设置AutoAck=false。
6 如何确保消息不重复消费?
-
利用数据库的唯一约束实现幂等
-
支持“INSERT IF NOT EXIST”语义的存储类系统,同样也可以基于Redis的SETNX命令来替代DB唯一约束来实现,即类似于分布式锁的功能
-
-
为更新的数据设置前置条件
-
类似于乐观锁,给数据增加一个版本号属性,每次更新前,先比较当前数据的版本号是否和MQ中消息的版本号一致,如果不一致就拒绝更新数据,一致就更新数据,同时将版本号+1
-
-
记录并检查操作(实现难度和复杂度较高,一般不建议使用)
-
在发送消息时,给每条消息指定一个全局唯一的ID,消费时先根据这个ID检查这条消息是否有被消费过,如果没有消费过,才更新数据,然后再将消费状态置为已消费
-
难点:消费端的三个步骤“检查消费状态,然后更新数据 并且 设置消费状态”必须保证原子性,才能实现真正的幂等!
-
7 如何处理消息积压?
-
发送端性能优化
-
检查是不是发送消息之前的业务逻辑耗时太多导致的
-
解法:设置合适的并发和批量的大小
-
-
消费端性能优化
-
大部分的性能问题都是出现在消费端,消费端的消费速度跟不上发送端的生产速度导致的
-
在设计系统时,提前考虑保证消费端的消费性能要高于生产端的发送性能,这样系统才能持续健康运行
-
解法:一是优化消费业务逻辑,二是通过水平扩容增加消费端的并发数来提高总体消费性能
-
要点:在扩容消费端实例的同时,必须同步扩容主题中的分区(也即队列)数量,确保消费端的实例数和分区数是相等的
-
-
发送变快了
-
消费变慢了
-
如果是单位时间发送的消息增多(比如双11,618大促),短时间内无法优化消费端代码,只有通过扩容消费端的实例来提升总体消费能力
-
如果短时间内没有足够的服务器资源扩容,那只有将系统降级,通过关闭一些不重要的业务,减少发送方发送的数据量,保证核心业务的稳定性
-
-
如果发送消息和消费消息的速度没什么变化,这时需要检查一下消费端,看看是不是消费失败导致的一条消息反复消费,这种情况也会拖慢整个系统的消费速度
-
如果是消费变慢了,需要检查消费端实例,检查日志是否有大量的消费错误,或者打印堆栈信息看看消费线程是否在什么地方卡主不动了,比如触发了死锁或等待某些资源
8 如何保证消息的顺序
-
在发送端使用账户ID作为Key,采用一致性哈希计算出队列编号,指定队列来发送消息
-
一致性哈希算法可以保证,相同的Key的消息总是发送到同一个队列上,从而保证相同Key的消息是严格有序的
-
如果不考虑队列扩容,也可以用队列数量取模的简单哈希算法来计算队列编号
Ref 参考资料
极客时间,李玥《消息队列高手课》