消息队列知识图谱
为什么需要消息队列
以秒杀系统设计为例,说明消息队列的应用场景。
异步处理
举例 :订单下单的异步化处理
存在的优点:
- 可以更快地返回结果;
- 减少等待,自然实现了步骤之间的并发,提升系统总体的性能
流量控制(削峰)
举例: 如何避免过多的请求压垮我们的秒杀系统?使用消息队列隔离网关和后端服务,以达到流量控制和保护后端服务的目的
加入消息队列后,整个秒杀流程变为:
1、网关在收到请求后,将请求放入请求消息队列;
2、后端服务从请求消息队列中获取 APP 请求,完成后续秒杀处理过程,然后返回结果
或者使用消息队列充当令牌桶的作用
这样做的好处,可以避免大量的请求给后端服务增加过大的压力,而且可以比较方便的扩充后端服务的实例。但同时带来的缺点是:
1、增加了系统调用链环节,导致总体的响应时延变长。
2、上下游系统都要将同步调用改为异步消息,增加了系统的复杂度。
服务解耦
消息队列的另外一个作用,就是实现系统应用之间的解耦。再举一个电商的例子来说明解耦的作用和必要性。
直接调用的形式:
消息队列解耦
除此之外消息队列还有如下应用场景:
1、作为发布 / 订阅系统实现一个微服务级系统间的观察者模式;
2、连接流计算任务和数据;
3、用于将消息广播给大量接收者。
同时在了解消息队列的应用场景下,我们还应当认识到消息队列自身的一些局限性
1、引入消息队列带来的延迟问题;
2、增加了系统的复杂度;
3、可能产生数据不一致的问题。如要保持强一致性,需要高代价的补偿(如分布式事务、对账),有数据丢失风险,如宕机重启,如要保证队列数据可用,需要额外机制保证(如双活容灾)
其他的问题
**问题1 :**MQ有消息堆积的能力,Redis作为一个高性能缓存有时候也作为队列来使用,那是否适合使用Redis来替代MQ应对消息堆积呢?
解答:Redis高性能是相对于主流数据库而言,一般能达到几万TPS,而现代消息队列很轻松可以达到十几万TPS;如果挤压的消息比较多,这时候需要考虑选择的MQ消息堆积的能力,在集中主流的消息队列中,RocketMQ,Kafka,Plusar要比RabbitMQ
该如何选择消息队列
选择消息队列的基本标准
必须是开源的
选择的产品必须是流行且社区活跃度比较高
另外作为消息队列,还应该具备如下几个要求
消息的可靠传递:确保不丢消息
Cluster:支持集群,确保不会因为某个节点宕机导致服务不可用,当然也不能丢消息
性能:具备足够好的性能,能满足绝大多数场景的性能要求。
RabbitMQ
-
开发语言:Erlang
-
架构
-
支持协议: AMQP
-
缺点:第一个问题是,RabbitMQ 对消息堆积的支持并不好;第二个问题是,RabbitMQ 的性能是我们介绍的这几个消息队列中最差的,依据硬件配置的不同,它大概每秒钟可以处理几万到十几万条消息。
选择中间件的考量维度:可靠性,性能,功能,可运维行,可拓展性,是否开源及社区活跃度
rabbitmq:
优点:轻量,迅捷,容易部署和使用,拥有灵活的路由配置
缺点:性能和吞吐量较差,不易进行二次开发
rocketmq:
优点:性能好,稳定可靠,有活跃的中文社区,特点响应快
缺点:兼容性较差,但随意影响力的扩大,该问题会有改善
kafka:
优点:拥有强大的性能及吞吐量,兼容性很好
缺点:由于“攒一波再处理”导致延迟比较高
pulsar:
采用存储和计算分离的设计,是消息队里产品中黑马,值得持续关注
消息模型:主题和队列有什么区别?
队列模型
主题模型
RocketMQ消息模型
topic :一类消息,如订单付款成功消息
Queue:一个topic可以对应多个队列,同一个消费者组的消费者只能消费一个队列的消息,消费组维护自己在队列中读取消息的位置。
消费者组
生产者
RocketMQ如何保证消息的顺序性:
消息生产者根据消息的一些标识信息,如订单ID,用户id通过一致性hash算法保证消息发送到相同的队列上。就可以确保严格顺序了。
RocketMQ如何保证消息的不丢失:
通过消息确认机制,为了不影响消息的发送效率,rocket是批量确认消息的。
消息队列是如何实现分布式事务的?
如何确保消息不会丢失?
检测消息丢失的方法
像 Kafka 和 RocketMQ 这样的消息队列,它是不保证在 Topic 上的严格顺序的,只能保证分区上的消息是有序的,所以我们在发消息的时候必须要指定分区,并且,在每个分区单独检测消息序号的连续性。
消息丢失可能发生的位置
生产阶段: 在这个阶段,从消息在 Producer 创建出来,经过网络传输发送到 Broker 端。
存储阶段: 在这个阶段,消息在 Broker 端存储,如果是集群,消息会在这个阶段被复制到其他的副本上。
消费阶段: 在这个阶段,Consumer 从 Broker 上拉取消息,经过网络传输发送到 Consumer 上。
生产阶段
对消息的发送结果进行检查
异步发送时需要注意重新发送回调函数
设置producer的重试策略
Broker 存储阶段
默认情况下,消息只要到了 Broker 端,将会优先保存到内存中,然后立刻返回确认响应给生产者。随后 Broker 定期批量的将一组消息从内存异步刷入磁盘。
这种方式减少 I/O 次数,可以取得更好的性能,但是如果发生机器掉电,异常宕机等情况,消息还未及时刷入磁盘,就会出现丢失消息的情况。
结合生产阶段与存储阶段,若需要严格保证消息不丢失,broker 需要采用如下配置:
消费阶段
消费者从 broker 拉取消息,然后执行相应的业务逻辑。一旦执行成功,将会返回ConsumeConcurrentlyStatus.CONSUME_SUCCESS 状态给 Broker。
如何处理消费过程中的重复消息
我们现在常用的绝大部分消息队列提供的服务质量都是 At least once,包括 RocketMQ、RabbitMQ 和 Kafka 都是这样。也就是说,消息队列很难保证消息不重复。我们现在常用的绝大部分消息队列提供的服务质量都是 At least once,包括 RocketMQ、RabbitMQ 和 Kafka 都是这样。也就是说,消息队列很难保证消息不重复。
幂等性解决方案
利用数据库的唯一约束实现幂等
在分库分表的情况下存在问题
查询判断再修改
存在并发安全问题
分布式锁方案
在锁过期的情况下,异常情况,如网络不稳定还是存在多次执行的可能。
分布式锁加日志判断方案
总结
如何实现高性能的分布式程序
- 使用异步设计的方法
- 异步网络IO
- 专用序列化、反序列化方法
- 设计良好的传输协议
- 双工通讯
Kafka如何实现高性能IO?
使用批量发送消费机制
kafka在发送端的SDK中,不管是producer是发送一条很少多条消息,客户端都是先缓存到内存中再批量发送到broker,broker也不对这些消息进行解包,一次性存下来。而在消费端sdk,也是批量获取消息,再有消费端来解包,一条条处理。所有broker的压力比较小。