乐趣区

消息队列技术点梳理(思维导图版)

消息队列作为服务 / 应用之间的通信中间件,可以起到业务耦合、广播消息、保证最终一致性以及错峰流控(克服短板瓶颈)等作用。本文不打算详细深入讲解消息队列,而是体系化的梳理消息队列可能涉及的技术点,起到提纲挈领的作用,构造一个宏观的概念,使用思维导图梳理。
再介绍之前,先简短比较下 RPC 和消息队列。RPC 大多属于请求 - 应答模式,也包括越来越多响应式范式,对于需要点对点交互、强事务保证和延迟敏感的服务 / 应用之间的通信,RPC 是优于消息队列的。那么消息队列(下文也简称 MQ,即 Message Queue)可以看做是一种异步 RPC,把一次 RPC 变为两次,进行内容转存,再在合适的时机投递出去。消息队列中间件往往是一个分布式系统,内部组件间的通信仍然会用到 RPC。
目前开源界用的比较多的选型包括,ActiveMQ、RabbitMQ、Kafka、阿里巴巴的 Notify、MetaQ、RocketMQ。下文的技术点梳理也是学习借鉴了这些开源组件,然后萃取出一些通用技术点。
关于消息队列的体系化认知,见下方的思维导图。

1. 整体架构一般分为 producer,broker,consumer 三者。
2. RPC 通信详细参考《体系化认识 RPC》。
3. 高性能保证主要考虑 MQ 的延迟和吞吐。
高性能投递方面,分为 producer 和 broker 考虑。producer 可以同步变异步、单条变批量保证发送端高性能,批量发送的触发条件可以分为 buffer 满或者时间窗口到了。broker 可以进行多 topic 划分,再多分区 /queue 来进行分治(Divide and Conquer)策略,加大并行度,分散投递压力。另外 broker 对于需要持久化的消息,可以使用顺序 IO,page cache,异步刷盘等技术提高性能,但是异步刷盘在掉电的情况下,可能会丢失数据,可以结合下面的高可用方案,在数据严格不丢和高性能吞吐之间做折中。
高性能消费,即 consumer 和 broker 通信,进行推、拉消息。使用 consumer group 水平扩展消费能力,需要按照业务场景使用分区有序或者无序消费。零拷贝技术节省 broker 端用户态到内核态的数据拷贝,直接从 page cache 发送到网络,从而最大化发送性能。consumer 批量 pull,broker 批量 push。broker 端还可以做消息过滤,可通过 tag 或者插件实现。
4. 高可用保证主要针对 broker 而言。
集群高可用,producer 通过 broker 投递消息,所以必然有且仅有一个 broker 主负责“写”,选主策略分为自动选主和非主动选择,自动选主使用分布一致性组件完成,例如 Kafka 使用 zookeeper,非自动选主,例如 RocketMQ 依赖多个无状态的 name server。
数据高可用,针对 broker 持久化积压消息场景。可借助分布式存储完成,但是往往性能上是个短板,所以大多数主流产品都进行本地 IO 顺序写,进行主从备份,多副本拷贝保证可用性,例如 RocketMQ 分为同步双写和异步复制,前者像 HDFS 一样,写完多个副本再返回 producer 成功,有一定性能损失,但不大,后者最大化性能,但是当主挂的时候,数据有丢失风险。
同样,MQ 集群也需要考虑跨机房高可用(非“异地多活”),broker 的写高可用,要考虑最小化 MTTR,同时不阻塞 consumer 消费。
5. 扩展性保证采用分治(Divide and Conquer)策略,加大投递和消费的并行度,多个 topic、多个分区 /queue、多个副本、多个 slave 或者镜像。
6. 协议 producer、consumer 和 broker 通信的协议,包括 AMQP、STOMP、MQTT、HTTP、OpenWire(ActiveMQ)、XMPP、自定义等等。
AMQP 是比较全面和复杂的一个协议,包括协议本身以及模型(broker、exchange、routing key 等概念),目前 RabbitMQ 是 AMQP 消息队列最有名的开源实现,有非常多语言已经支持基于 AMQP 协议与消息队列通信,同时还可以通过插件支持 STOMP、MQTT 等协议接入。Kafka、RocketMQ 均使用自定义的协议。
7. 消费关系包括三种
1) 点对点,也就是 P2P,FIFO 的队列,可以看做单播。
2) Topic 模式,Pub/Sub 发布订阅。
3) fanout 广播模式。
8. 消息堆积能力持久化消息,如果存储在本地磁盘,可以使用同步刷盘和异步刷盘两种策略。磁盘不能无限堆积,会有清理策略,例如 Kafka、RocketMQ 都按照时间、数据量进行 retention。
非持久化,仅放在内存,消费者处理完可选择删除掉。
9. 可靠投递对于 producer,从 API 和 I / O 层面可使用同步、异步,对于吞吐层面可使用单条、批量。fire-and-forget 模式,类似 UDP,尽管发送即可。针对可能发生的错误,例如连接 broker 失败,RPC 超时、发布消息失败、发布后无响应,可选择忽略或者重发,所以往往重复投递的情况不可避免。
对于 broker,如果要保证数据 100% 不丢,是可能的,但是需要牺牲下性能和吞吐,使用同步多写、多副本策略 + 同步刷盘持久化消息,可以严格保证不丢。另外,broker 对于写入消息的 payload,也会做完整性校验,例如 CRC 等。
10. 可靠消费消费次数,包括 at most once、at least once、exactly once,其中前两个比较好做到,最后的 exactly once 需要 streaming consumer 系统和 broker 端协作完成,例如 storm 的 trident 和 flink。
推拉模式,push or pull。推模式最小化投递延迟,但是没有考虑 consumer 的承载能力,拉一般是轮询接收 broker 的数据,按照 consumer 自己的能力消费。
消费记录点,一般每个消息都有一个 offset、ID 或者时间戳,consumer 可以按照这个 offset 来进行定点消费以及消息重放。
消息确认,consumer 消费完成 ACK 回调 broker 或者集群高可用中间件(zk)通知消费进度。
错误处理,对于消费失败的情况,可以回复 NACK,要求重发 /requeue 消息,当错误超多一定阈值时候,放到死信队列中。
消息重复消费,这和消费次数有关系,consumer 在某些时候需要做到幂等性,保证重复消费不会引起业务异常。
11. 消息类型顺序消息,有序的话,分为分区有序或者全局有序,前者可以按照某个业务 ID 取模,在发送端发到不同的分区 /queue 即可,后者往往需要单个队列才可以满足。无序消费则可最大化吞吐。
定时消息,事务消息,例如 RocketMQ 均支持。
12. 消息查询目前 RocketMQ 支持消息根据 msgId 查询。
13. 生态融合客户端语言的丰富性,与其他系统的集成度,例如 Kafka 和大数据技术栈融合很紧密,Spark、Storm、Flink、Kylin 都有对应的 connector。
14. 管理工具分布式系统的管理是提高生产效率的必备保障,一个好的系统,如果周边工具不完善,对于使用者会很不友好,推广也会有困难。
对于消息队列,可以从 topic 管理、broker 管理、集群管理、权限 / 配额管理、多租户、客户端工具、监控、报警、控制台 Console UI 来全方位进行治理。
总结由于笔者经验所限,已尽可能广泛且全面的梳理,日后随着认识的深入,会不断的更新材料,也欢迎读者指出问题,欢迎交流。
欢迎工作一到五年的 Java 工程师朋友们加入 Java 进阶架构学习交流:952124565 群内提供免费的 Java 架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm 性能调优、Spring 源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx 等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己。

退出移动版