RocketMQ 是什么
RocketMQ 是阿里巴巴在 2012 年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于 2017 年 9 月 25 日成为 Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。
淘宝内部的交易系统使用了淘宝自主研发的 Notify 消息中间件,使用 MySQL 作为消息存储媒介,可完全水平扩容,为了进一步降低成本,我们认为存储部分可以进一步优化,2011 年初,Linkin开源了 Kafka这个优秀的消息中间件,淘宝中间件团队在对 Kafka 做过充分 Review 之后, Kafka无限消息堆积,高效的持久化速度吸引了我们,但是同时发现这个消息系统主要定位于日志传输,对于使用在淘宝交易、订单、充值等场景下还有诸多特性不满足,为此我们重新用 Java 语言编写了 RocketMQ ,定位于非日志的可靠消息传输(日志场景也OK),目前 RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理, binglog 分发等场景。
RocketMQ 角色组成
四部分构成:
- 生产者(Producer):负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。
- 消费者(Consumer):负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。
- 消息服务器(Broker):是消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息。
- 名称服务器(NameServer):用来保存 Broker 相关 Topic 等元信息并给 Producer 、Consumer 查找 Broker 信息。主要功能是为整个MQ集群提供服务协调与治理,具体就是记录维护Topic、Broker的信息,及监控Broker的运行状态。
RocketMQ 的整体流程
-
启动 Namesrv,Namesrv起 来后监听端口,等待 Broker、Producer、Consumer 连上来,相当于一个路由控制中心。
-
Broker 启动,跟所有的 Namesrv 保持长连接,定时发送心跳包。心跳包中,包含当前 Broker 信息(IP+端口等)以及存储所有 Topic 信息。注册成功后,Namesrv 集群中就有 Topic 跟 Broker 的映射关系。
-
收发消息前,先创建 Topic 。创建 Topic 时,需要指定该 Topic 要存储在 哪些 Broker上。也可以在发送消息时自动创建Topic。
-
Producer 发送消息。
启动时,先跟 Namesrv 集群中的其中一台建立长连接,并从Namesrv 中获取当前发送的 Topic 存在哪些 Broker 上,然后跟对应的 Broker 建立长连接,直接向 Broker 发消息。 -
Consumer 消费消息。
Consumer 跟 Producer 类似。跟其中一台 Namesrv 建立长连接,获取当前订阅 Topic 存在哪些 Broker 上,然后直接跟 Broker 建立连接通道,开始消费消息。
Namesrver
- Namesrv 用于存储 Topic、Broker 关系信息,功能简单,稳定性高。
- 多个 Namesrv 之间相互没有通信,单台 Namesrv 宕机不影响其它 Namesrv 与集群。
- 多个 Namesrv 之间的信息共享,通过 Broker 主动向多个 Namesrv 都发起心跳。正如上文所说,Broker 需要跟所有 Namesrv 连接。
- 即使整个 Namesrv 集群宕机,已经正常工作的 Producer、Consumer、Broker 仍然能正常工作,但新起的 Producer、Consumer、Broker 就无法工作。
- Namesrv 压力不会太大,平时主要开销是在维持心跳和提供 Topic-Broker 的关系数据。但有一点需要注意,Broker 向 Namesr 发心跳时,会带上当前自己所负责的所有 Topic 信息,如果 Topic 个数太多(万级别),会导致一次心跳中,Topic 的数据就几十 M,网络情况差的话,网络传输失败,心跳失败,导致 Namesrv 误认为 Broker 心跳失败。
另外,内网环境网络相对是比较稳定的,传输几十 M 问题不大。同时,如果真的要优化,Broker 可以把心跳包做压缩,再发送给 Namesrv 。不过,这样也会带来 CPU 的占用率的提升。
配置 Namesrv 地址到生产者和消费者
将 Namesrv 地址列表提供给客户端( 生产者和消费者 ),有四种方法:
- 编程方式,就像 producer.setNamesrvAddr(“ip:port”) 。
- Java 启动参数设置,使用 rocketmq.namesrv.addr 。
- 环境变量,使用 NAMESRV_ADDR 。
- HTTP 端点,例如说:http://namesrv.rocketmq.xxx.com 地址,通过 DNS 解析获得 Namesrv 真正的地址。
Broker
- 高并发读写服务。
Broker的高并发读写主要是依靠以下两点:
- 消息顺序写,所有 Topic 数据同时只会写一个文件,一个文件满1G ,再写新文件,真正的顺序写盘,使得发消息 TPS 大幅提高。
- 消息随机读,RocketMQ 尽可能让读命中系统 Pagecache ,因为操作系统访问 Pagecache 时,即使只访问 1K 的消息,系统也会提前预读出更多的数据,在下次读时就可能命中 Pagecache ,减少 IO 操作。
- 负载均衡与动态伸缩。
- 负载均衡:Broker 上存 Topic 信息,Topic 由多个队列组成,队列会平均分散在多个 Broker 上,而 Producer 的发送机制保证消息尽量平均分布到所有队列中,最终效果就是所有消息都平均落在每个 Broker 上。
- 动态伸缩能力(非顺序消息):Broker 的伸缩性体现在两个维度:Topic、Broker。
Topic 维度: 假如一个 Topic 的消息量特别大,但集群水位压力还是很低,就可以扩大该 Topic 的队列数, Topic 的队列数跟发送、消费速度成正比。
Broker 维度: 如果集群水位很高了,需要扩容,直接加机器部署 Broker 就可以。Broker 启动后向 Namesrv 注册,Producer、Consumer 通过 Namesrv 发现新Broker,立即跟该 Broker 直连,收发消息。
新增的 Broker 想要下线,想要下线也比较麻烦,暂时没特别好的方案。大体的前提是,消费者消费完该 Broker 的消息,生产者不往这个 Broker 发送消息。
- 高可用 & 高可靠。
- 高可用:集群部署时一般都为主备,备机实时从主机同步消息,如果其中一个主机宕机,备机提供消费服务,但不提供写服务。
- 高可靠:所有发往 Broker 的消息,有同步刷盘和异步刷盘机制。
- 同步刷盘时,消息写入物理文件才会返回成功。
- 异步刷盘时,只有机器宕机,才会产生消息丢失,Broker 挂掉可能会发生,但是机器宕机崩溃是很少发生的,除非突然断电。如果 Broker 挂掉,未同步到硬盘的消息,还在 Pagecache 中呆着。
4、 Broker 与 Namesrv 的心跳机制。
- 单个 Broker 跟所有 Namesrv 保持心跳请求,心跳间隔为30秒,心跳请求中包括当前 Broker 所有的 Topic 信息。
- Namesrv 会反查 Broker 的心跳信息,如果某个 Broker 在 2 分钟之内都没有心跳,则认为该 Broker 下线,调整 Topic 跟 Broker 的对应关系。但此时 Namesrv 不会主动通知Producer、Consumer 有 Broker 宕机。也就说,只能等 Producer、Consumer 下次定时拉取 Topic 信息的时候,才会发现有 Broker 宕机。
从上面的描述中,我们也已经发现 Broker 是 RocketMQ 中最最最复杂的角色,主要包括如下五个模块:
Broker 组件图
- 远程处理模块:是 Broker 的入口,处理来自客户的请求。
- Client Manager :管理客户端(生产者/消费者),并维护消费者的主题订阅。
- Store Service :提供简单的 API 来存储或查询物理磁盘中的消息。
- HA 服务:提供主节点和从节点之间的数据同步功能。
- 索引服务:通过指定键为消息建立索引,并提供快速的消息查询。
Producer
- 获得 Topic-Broker 的映射关系。
- Producer 启动时,也需要指定 Namesrv 的地址,从 Namesrv 集群中选一台建立长连接。如果该 Namesrv 宕机,会自动连其他 Namesrv ,直到有可用的 Namesrv 为止。
- 生产者每 30 秒从 Namesrv 获取 Topic 跟 Broker 的映射关系,更新到本地内存中。然后再跟 Topic 涉及的所有 Broker 建立长连接,每隔 30 秒发一次心跳。
- 在 Broker 端也会每 10 秒扫描一次当前注册的 Producer ,如果发现某个 Producer 超过 2 分钟都没有发心跳,则断开连接。
-
生产者端的负载均衡。
生产者发送时,会自动轮询当前所有可发送的broker,一条消息发送成功,下次换另外一个broker发送,以达到消息平均落到所有的broker上。 -
消息发送的方式:同步方式、异步方式、Oneway 方式(单向)
Consumer
- 获得 Topic-Broker 的映射关系。
- Consumer 启动时需要指定 Namesrv 地址,与其中一个 Namesrv 建立长连接。消费者每隔 30 秒从 Namesrv 获取所有Topic 的最新队列情况,这意味着某个 Broker 如果宕机,客户端最多要 30 秒才能感知。连接建立后,从 Namesrv 中获取当前消费 Topic 所涉及的 Broker,直连 Broker 。
- Consumer 跟 Broker 是长连接,会每隔 30 秒发心跳信息到Broker 。Broker 端每 10 秒检查一次当前存活的 Consumer ,若发现某个 Consumer 2 分钟内没有心跳,就断开与该 Consumer 的连接,并且向该消费组的其他实例发送通知,触发该消费者集群的负载均衡。
- 消费者端的负载均衡。根据消费者的消费模式不同,负载均衡方式也不同。
消费者消费模式
消费者消费模式有两种:集群消费和广播消费。
- 集群消费
消费者的一种消费模式。一个 Consumer Group 中的各个 Consumer 实例分摊去消费消息,即一条消息只会投递到一个 Consumer Group 下面的一个实例。
- 每个 Consumer 是平均分摊 Message Queue 的去做拉取消费。例如某个 Topic 有 3 个队列,其中一个 Consumer Group 有 3 个实例(可能是 3 个进程,或者 3 台机器),那么每个实例只消费其中的 1 个队列。
- 由 Producer 发送消息的时候是轮询所有的队列,所以消息会平均散落在不同的队列上,可以认为队列上的消息是平均的。那么实例也就平均地消费消息了。
- 这种模式下,消费进度的存储会持久化到 Broker 。
- 当新建一个 Consumer Group 时,默认情况下,该分组的消费者会从 min offset 开始重新消费消息。
- 广播消费
消费者的一种消费模式。消息将对一 个Consumer Group 下的各个 Consumer 实例都投递一遍。即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。
- 一个消费组下的每个消费者实例都获取到了 Topic 下面的每个 Message Queue 去拉取消费。所以消息会投递到每个消费者实例。
- 这种模式下,消费进度会存储持久化到实例本地。
消费者获取消息模式
消费者获取消息有两种模式:推送模式和拉取模式。
-
PushConsumer
推送模式(虽然 RocketMQ 使用的是长轮询)的消费者。消息的能及时被消费。使用非常简单,内部已处理如线程池消费、流控、负载均衡、异常处理等等的各种场景。 -
PullConsumer
拉取模式的消费者。应用主动控制拉取的时机,怎么拉取,怎么消费等。主动权更高。但要自己处理各种场景。