一, 发展历程
早期淘宝内部有两套消息中间件系统: Notify 和 Napoli. 先有的 Notify(至今 12 历史), 后来因有序场景需求, 且恰好当时 Kafka 开源(2011 年), 所以参照 Kafka 的设计理念自研了 RocketMQ. 目前 Notify 和 RocketMQ 二者的定位如下:
RocketQ 主要面向消息有序的场景, 能够提供更大的消息堆积能力, 拉模式, 消息持久化在磁盘
Notify 主要面向更加安全可靠地交易类场景, 无序, 推模式, 消息持久化在 MySQL
RocketMQ 发展历程如下:
Metaq 1.x 开源社区维护 killme2008 维护, 因为依赖 zk 挂了, 导致上下游服务全网宕机, 到了 12 年基于开源 Kafka, 直接用 java 语言翻译重写
Metaq 2.x 2012 年 11 月上线, 淘宝内部使用
RocketMQ 3.x 后来一统江湖成为整个阿里系主流 MQ. 基于公司内部开源共建原则, RocketMQ 项目只维护核心功能, 且去除了所有其他运行时依赖, 核心功能最简化. 每个 BU 的个性化需求都在 RocketMQ 项目之上进行深度定制. RocketMQ 向其他 BU 提供的仅仅是 Jar 包, 例如要定制一个 Broker, 那么只需要依赖 rocketmq-broker 这个 jar 包即可, 可通过 API 进行交互, 如果定制 client, 则依赖 rocketmq-client 这个 jar 包, 对其提供的 API 进行再封装.
RocketMQ 4.x.x 捐献给 Apache 社区, 经过一年时间重构孵化成为顶级项目
Metaq 改名为 RocketMQ,RocketMQ 项目做核心功能, 淘宝内部其他个性化需求有做定制化开发, 如:
com.taobao.metaq v3.0 (为淘宝应用提供消息服务 ) com.alipay.zpullmsg v1.0 (为支付宝应用提供消息服务) com.alibaba.commonmq v1.0 (为 B2B 应用提供消息服务)
RocketMQ 一共经历了三代里程碑演进:
Notify 为阿里系第一代 MQ 产品. 推模式, 数据存储采用关系型数据库.
Metaq 为阿里系第二代 MQ 产品. 拉模式, 自研的专有消息存储, 在日志处理方面参考 Kafka, 典型代表 MetaQ.
RocketMQ 为阿里系第三代 MQ 产品. 以拉模式为主, 兼有推模式, 低延迟消息引擎 RocketMQ, 在二代功能特性的基础上, 为电商金融领域添加了可靠重试, 基于文件存储的分布式事务等特性. 使用在了阿里大量的应用上, 典型如双 11 场景, 具有万亿级消息堆积能力.
RocketMQ 项目根据开源与商业分成 2 个版本:
Apache RocketMQ 开源版
2013 年, 阿里云 ONS(功能相比较更齐全, 特别是运维体系完善, 例如: 运维管控, 安全授权, 深度培训等纳入商业重中之重)
2015 年, Aliware MQ(Message Queue)是 RocketMQ 的商业版本, 是阿里云商用的专业消息中间件, 是企业级互联网架构的核心产品, 基于高可用分布式集群技术, 搭建了包括发布订阅, 消息轨迹, 资源统计, 定时(延时), 监控报警等一套完整的消息云服务.
RocketMQ 项目根据开源与商业分成 2 个版本:
Apache RocketMQ 是对外开源版
2013 年, 阿里云 ONS(功能相比较更齐全, 特别是运维体系完善, 例如: 运维管控, 安全授权, 深度培训等纳入商业重中之重)
2015 年, Aliware MQ(Message Queue)是 RocketMQ 的商业版本, 是阿里云商用的专业消息中间件, 是企业级互联网架构的核心产品, 基于高可用分布式集群技术, 搭建了包括发布订阅, 消息轨迹, 资源统计, 定时(延时), 监控报警等一套完整的消息云服务.
二, 系统架构
系统定位
是一个队列模型的消息中间件, 具有高性能, 高可靠, 高实时, 分布式特点
同时支持 Push 与 Pull 方式消费消息
能支撑天猫双十一海量消息考验
能够保证严格的消息顺序
提供丰富的消息拉取模式
高效的订阅者水平扩展能力
亿级消息堆积能力
四种集群部署方式:
单 master (缺点: broker 宕机, 服务不可用)
多 master 无 slave (缺点: 单台机器宕机期间, 这台机器上未被消费的消息在机器恢复之前不可订阅)
多 master 多 slave, 异步复制 (缺点: Master 宕机, 磁盘损坏情况, 可能会丢失少量消息)
多 master 多 slave, 同步双写(缺点: 性能比异步复制模式略低, 大约低 10% 左右)
生产环境部署都是多主多从. 下面以 2 主 2 从为例
组件角色
Producer: 消息发布的角色, 支持分布式集群方式部署. 与 NameServer(随机)中的其中一个节点建立长链接, 定期获取 Topic 路由信息, 并向提供 Topic 服务的 Master 建立长链接, 另外和 Master 之间做心跳. Producer 通过 MQ 的负载均衡模块选择相应的 Broker 集群队列进行消息投递, 投递的过程支持快速失败并且低延迟.
Consumer: 消息消费的角色, 支持分布式集群方式部署. 与 NameServer(随机)中的其中一个节点建立长链接, 定期获取 Topic 路由信息, 并向提供 topic 服务的 Master,Slave 建立长连接 , 由 Broker 配置订阅规则. 支持以 push 推, pull 拉两种模式对消息进行消费. 同时也支持集群方式和广播方式的消费, 它提供实时消息订阅机制, 可以满足大多数用户的需求.
NameServer:NameServer 是一个非常简单的 Topic 路由注册中心, 其角色类似 Dubbo 中的 zookeeper, 支持 Broker 的动态注册与发现. 主要包括两个功能: Broker 管理, NameServer 接受 Broker 集群的注册信息并且保存下来作为路由信息的基本数据. 然后提供心跳检测机制, 检查 Broker 是否还存活; 路由信息管理, 每个 NameServer 将保存关于 Broker 集群的整个路由信息和用于客户端查询的队列信息. 然后 Producer 和 Conumser 通过 NameServer 就可以知道整个 Broker 集群的路由信息, 从而进行消息的投递和消费. NameServer 通常也是集群的方式部署, 各实例间相互不进行信息通讯. Broker 是向每一台 NameServer 注册自己的路由信息, 所以每一个 NameServer 实例上面都保存一份完整的路由信息. 当某个 NameServer 因某种原因下线了, Broker 仍然可以向其它 NameServer 同步其路由信息, Producer,Consumer 仍然可以动态感知 Broker 的路由的信息.
BrokerServer:Broker 主要负责消息的存储, 投递和查询以及服务高可用保证, 为了实现这些功能, Broker 包含了以下 5 个重要子模块:
Remoting Module: 整个 Broker 的实体, 负责处理来自 clients 端的请求.
Client Manager: 负责管理客户端 (Producer/Consumer) 和维护 Consumer 的 Topic 订阅信息
Store Service: 提供方便简单的 API 接口处理消息存储到物理硬盘和查询功能.
HA Service: 高可用服务, 提供 Master Broker 和 Slave Broker 之间的数据同步功能.
Index Service: 根据特定的 Message key 对投递到 Broker 的消息进行索引服务, 以提供消息的快速查询.
三, 关键特性
1. 单机支持 1 万以上持久化队列
顺序写, 随机读. consumerQueue 是逻辑队列存储元数据信息, commitlog 负责存储消息, consumerQueue 只存储消息在 commitlog 中的位置信息, 定长存储, 支持串行方式刷盘.
2. 刷盘策略
同步刷盘
异步刷盘
二者的区别在于是写完 PageCache 直接返回, 还是刷盘后返回
3. 消息查询 / 消息回溯
支持 MessageID 和 MessageKey 查询.(业务场景: 如某个订单处理失败, 是消息没收到还是收到处理出错了)
按照时间来回溯消息, 精度毫秒.(业务场景: 订单分析, 程序 bug, 导致今天从某个时间点的消息需要重新开始消费)
4. 消息过滤
Broker 端(tag 的哈希值比对, 丢到对应的 consumeQueue 中) consumer 端(直接和 tag 比)
5. 消息获取机制
本质上都是 Pull 机制(据官方资料显示其中 PushConsumer 的实时性接近于 push).
PushConsumer: consumer 通过长轮询拉取消息后回调 MessageListener 接口完成消费, 业务只需要完成 MessageListener 完成业务逻辑即可.(注册监听回调, 一个线程专门长轮训从 broker 端拉消息, push 到一个本地可配置队列)辑即可.(注册监听回调, 一个线程专门长轮训从 broker 端拉消息, push 到一个本地可配置队列)
PullConsumer: 完全由业务系统去控制, 定时拉取消息, 指定队列消费, 主要由业务控制.
6. 单队列并行消费
单队列一批消息拉取到消费端, 既可以支持单线程串行有序消费, 也可以支持多线程乱序消费提高并发性能, 如下图所示:
采用滑动窗口方式并行消费, 多个线程消费, 提交 offset 都是最小 offset.
7. 消费负载均衡
都在客户端实现
Producer 端: 从 NameServer 获取 MessageQueue 列表, RR 选择具体的消息队列发送消息.
Consumer 端: 从 NameServer 获取 MessageQueue 列表和其他 Consumer 状态信息, 达到平均消费目的(consumer 超过队列数则处于空闲状态)
8. 顺序消息原理
在 RocketMQ 中, 主要指的是局部顺序, 即一类消息为满足顺序性, 必须 Producer 单线程顺序发送, 且发送到同一个队列, 这样 Consumer 就可以按照 Producer 发送 的顺序去消费消息.
普通顺序消息: Broker 重启, 队列总数发生变化, 导致哈希取模后定位队列变化, 导致短暂消息顺序不一致.
严格顺序消息: 只要一台机器不可用, 整个集群不可用.(同步双写保证)
9. 事务支持
RocketMQ 采用了 2PC 的方案来提交事务消息, 同时增加一个补偿逻辑来处理二阶段超时或者失败的消息, 如下图所示:
上图说明了事务消息的大致方案, 分为两个逻辑: 正常事务消息的发送及提交, 事务消息的补偿流程
事务消息发送及提交:
发送消息(half 消息)
服务端响应消息写入结果
根据发送结果执行本地事务(如果写入失败, 此时 half 消息对业务不可见, 本地逻辑不执行)
根据本地事务状态执行 Commit 或者 Rollback(Commit 操作生成消息索引, 消息对消费者可见)
补偿流程:
对没有 Commit/Rollback 的事务消息(pending 状态的消息), 从服务端发起一次 "回查"
Producer 收到回查消息, 检查回查消息对应的本地事务的状态
根据本地事务状态, 重新 Commit 或者 Rollback
补偿阶段用于解决消息 Commit 或者 Rollback 发生超时或者失败的情况.
10. 延时消息
业务场景: 支付曾经提过延时消费需求(对应消费失败后, 延时多久再推送)
开源版本 RocketMQ 仅支持定时 Level(几个梯度的延时, 5s,10s,1min 等) 阿里云的 ONS 支持定时 level, 以及制定毫秒级别延时时间
11. 消息失败重试
Producer 端:
Producer 的 send 方法本身支持内部重试, 重试逻辑如下:
(1) 至多重试 3 次
(2) 如果发送失败, 则轮转到下一个 Broker
(3) 这个方法的总耗时时间不超过 sendMsgTimeout 设置的值, 默认 10s 所以, 如果本身向 broker 发送消息产生超时异常, 就不会再做重试. 再发送失败由应用层自己做.
Consumer 端:
广播模式: 发送失败的消息丢弃, 广播模式对于失败重试代价过高, 对整个集群性能会有较大影响, 失败重试功能交由应用处理 集群模式: 将消费失败的消息一条条的发送到 broker 的重试队列中去, 如果此时依然有发送到重试队列还是失败的消息, 那就在 cosumer 的本地线 程
定时 5 秒钟以后重试重新消费消息, 再走一次上面的消费流程.
12.Broker HA 机制
同步双写: HA 采用同步双写方式, 主备都写成功, 向应用返回成功.
异步复制: slave 启动一个线程, 不断从 master 拉取 commitlog 中的数据, 然后异步 build 出 ConsumeQueue 数据结构.
13. 死信队列
由于某些原因消息无法被正确的投递, 为了确保消息不会被无故的丢弃, 一般将其置于一个特殊角色的队列, 这个队列一般称之为死信队列. 与此对应的还有一个 "回退队列" 的概念, 试想如果消费者在消费时发生了异常, 那么就不会对这一次消费进行确认(Ack), 进而发生回滚消息的操作之后消息始终会放在队列的顶部, 然后不断被处理和回滚, 导致队列陷入死循环. 为了解决这个问题, 可以为每个队列设置一个回退队列, 它和死信队列都是为异常的处理提供的一种机制保障. 实际情况下, 回退队列的角色可以由死信队列和重试队列来扮演.
14. 重试队列
重试队列其实可以看成是一种回退队列, 具体指消费端消费消息失败时, 为防止消息无故丢失而重新将消息回滚到 Broker 中. 与回退队列不同的是重试队列一般分成多个重试等级, 每个重试等级一般也会设置重新投递延时, 重试次数越多投递延时就越大. 举个例子: 消息第一次消费失败入重试队列 Q1,Q1 的重新投递延迟为 5s, 在 5s 过后重新投递该消息; 如果消息再次消费失败则入重试队列 Q2,Q2 的重新投递延迟为 10s, 在 10s 过后再次投递该消息. 以此类推, 重试越多次重新投递的时间就越久, 为此需要设置一个上限, 超过投递次数就入死信队列. 重试队列与延迟队列有相同的地方, 都是需要设置延迟级别, 它们彼此的区别是: 延迟队列动作由内部触发, 重试队列动作由外部消费端触发; 延迟队列作用一次, 而重试队列的作用范围会向后传递.
四, 不足之处
RocketMQ 不管系统架构, 还是底层存储都有居多亮点, 以此来支撑强大的各种特性, 不可否认也有居多不足之处:
不支持 Master/Slave 自动切换. RocketMQ 开源版本目前还不支持把 Slave 自动转成 Master, 如果机器资源不足, 需要把 Slave 转成 Master, 则要手动停止 Slave 角色的 Broker, 更改配置文件, 用新的配置文件启动 Broker. 商业版本支持自动 master/slave 主从切换
不支持数据迁移, 对服务扩容不太友好, 也不灵活. 如果服务需要扩容, 只能增加服务器节点数了, 然后新增 queue 分配到新节点上. 如果新老机器负载不均衡, 要么多增加 queue 到新机器上, 要么替换性能不强的老旧机器
不支持多挂载点. 当今硬件发展日新月异, pc 服务器性能越来越强大, 一个物理机器会挂载很块多磁盘, 但一个 RocketMQ 实例却只能读写操作一个挂载点数据, 想榨干机器资源, 操作多挂载点需要部署多实例或依靠 docker 容器等来实现
博客地址引用: https://www.cnblogs.com/lizherui/p/12655425.html
来源: https://www.cnblogs.com/lizherui/p/12655425.html