本文作者:黄理,快手在线消息系统负责人。
快手对于 RocketMQ 社区版本的优化一般为在其外层进行能力的构建,而不是对其内部进行大改动,因为内部大改不利于后期的版本升级。即使对内部 RocketMQ 进行了修改,我们也会尽量通过 PR 将新特性回馈到社区。
快手会定期对 RocketMQ 进行升级。2022年春节,我们大胆使用了尚未正式发布的 RocketMQ 4.9.3-SNAPSHOT 版本,平稳度过了快手一年中最重要的活动,这也证实了社区版本 RocketMQ 的兼容性和稳定性。
应用篇
RocketMQ 进入快手两年内,从 0 发展到每天数千亿消息级别。快手是 RocketMQ 社区版本的事务消息最早的大规模用户,目前每天有百亿以上的事务消息和定时消息,实现了跨 IDC的自动负载均衡以及容灾,实现了多级泳道(项目)互不干扰,多个项目可同时开发,以及可回落。
RocketMQ的落地方式一般为两种。
方式一:在开源软件的基础上进行修改,能够快速轻松地实现需要的功能,但后续升级存在很大不便。
方式二:对RocketMQ的 client 和 server 只进行少量修改。如果 server 存在能力缺失,会开发辅助的 server 或以 proxy 进行补充。我们在 client 之上包装了一层 MQ SDK,对用户屏蔽了具体实现。快手也是基于此方式对RocketMQ进行了落地。
MQ SDK如上图,分为三层:最上层为 API ,用户与该层打交道;中间为核心层,能够实现各种通用的能力;最底层负责与具体的 MQ 交互,当前只有 RocketMQ 的实现,但未来也许会有其他消息中间件的实现。
中间层实现了热变更的能力。用户配置不写在代码里,而是在平台进行指定,指定以后热生效,SDK 会直接加载新的配置并自动 reload 用户程序,无需重启。
RocketMQ 会分配 Logic topic,业务代码无需关心集群在哪,无需关心当前环境,也无需关心数据标记比如压测、泳道,只需使用我们提供的非常简单的 API ,复杂的过程全部在核心层进行封装,对业务用户屏蔽。
上图为跨机房负载均衡的实现流程。
所有Logic topic 都会映射到上图中间的两个机房,每个机房部署一个集群。RocketMQ 集群可以有多个 broker ,生产者和消费者对 broker 有故障感知和转移能力,能够通过 NameServer 发现哪个 broker 故障以避免与其进行连接。为了实现更好的控制,我们在集群之上又封装了一层负载均衡,并在 client 端实现。
每一个 Logic topic 都被分配到两个集群,生产者在生产时会同时连接到两个集群。两个集群分别位于不同 IDC,同一地区的两个IDC之间延时大约为1-2ms。消费者侧也是双连,一旦某机房或集群发生故障,流量会立刻自动转移到另一机房。
我们将生产端的自动负载均衡(自动failover)进行了抽象封装,做成了开源项目。Failover 组件与 RocketMQ 无关,在主调方做 RPC 或消息生产等调用时,会检查被调方的健康度。如果被调方不健康或响应很慢,则会调低其权重。
扫描上方二维码,了解更多关于该组件的功能。
RocketMQ 实现了简单的延迟消息,但是只能实现几个固定级别的延时,而实际的业务更希望发消息时可以任意指定延迟时间。因此我们通过外挂 Delay Server 的方式实现了任意精度的延迟消息。
Delay Server 会将用户要求延迟投递的消息进行保存,等到指定时间以后,再将消息送回原来的业务 topic 。
基于PULL的模式(RocketMQ的PUSH Consumer也是基于PULL),很难直接实现动态的多泳道隔离。因此很多公司会选择根据泳道的不同将消息投递到不同的 topic,这样虽然实现了功能,但是侵入性太强,并且也不好实现多级回落。
而在快手,我们将所有泳道的消费都放在同一个 topic 里,并且实现了多级泳道回落。比如项目 A 和项目 B 同时在开发,项目 A 生产的消息由项目 A 的消费者消费。项目 A 下面有子项目A.X和A.Y,那么A.X生产的消息由A.X的消费者消费,但是如果A.Y没有消费者,因此A.Y生产的消息会回落到由 A 的消费者进行消费。
快手的泳道隔离主要通过 RPC 转发实现。
如上图,黄色线代表项目 A 的数据流向,橙色线代表项目 B 的数据流向。SDK 会在将数据交给业务用户之前,先检查数据的泳道标记是否匹配,如果不匹配,则会通过 RPC 的方式转发至匹配的消费者。
这样我们等于是将 PULL 转为了 PUSH,在主调方进行选择,所以能够将所有项目放在同一个 topic 里,且能够实现隔离。
RocketMQ 是基于 queue 来进行客户端的 rebanance,如果消费者的数量大于queue,则会导致部分消费者无事可做;或有时 queue 分配不均匀,导致部分消费者负担大、部分消费者负担小。
我们通过RPC的方式实现了消费 Proxy,先从 RocketMQ 的 broker 消费数据,再通过 RPC 的方式 PUSH 到 consumer。
消费 proxy 的 RPC 与前文的 RPC 转发使用同一套机制,因此RocketMQ 不需要过多的 queue, 但可以有很多消费者。消费 proxy 为可选项,只需在平台指定消费方式,无需修改代码也无需重启,即可直接热生效。
此外,我们开发了拨测程序,生成了一个模拟的生产者和消费者。生产者会往线上所有集群的每一个 broker 发送消息,包括普通消息、定时消息和事务消息。消费者消费到消息后,会取出消息体内生产者的 IP 地址,然后通过 TCP 的方式 ACK 回生产者,告知生产者消息是否丢失、是否重复以及从生产到消费的延迟是多少。
另外,还可以针对生产者和消费者进行各种配置,比如消息体大小、生产速率、对账抽样比例、对账周期等。比如在配置中将速率调大,即可实现压测,生产者和消费者会进行打点,最终可在 Grafana 上进行查看和告警。
性能篇
对 RocketMQ 进行性能优化后,300字节小消息生产 TPS 提升54%,同时 CPU占用降低11%;600 queue消费性能提升200%,跨IDC 100KB大消息单线程生产 TPS 提升693%,跨IDC 100KB集群消费吞吐提升250%。
优化主要分为两批。
第一批优化集中在RocketMQ4.9.1版本,包括清除多余的日志、消除不必要的锁、消除主从复制中的数组拷贝、优化Broker的默认参数、优化锁内操作的性能并将部分操作抽取到锁外、优化消息属性编解码的性能以及优化消息Header解析的性能。扫描二维码可阅读本次优化相关的完整文章。
另外,RocketMQ 4.9.0版本的默认参数设置不合理,因此我们在4.9.1版本对其进行了优化,使得性能有了巨大提升。
上图为第二批优化的目标:
- 降低 CPU 开销。RocketMQ 不消耗 CPU,但是在混合部署场景下,CPU 极有可能会成为问题,因此需要对 CPU 开销进行优化。
- 提升跨机房的生产、消费的吞吐。
- 提升大消息的吞吐。
- 提升queue特别多的场景下的消费性能。
第二批优化大部分集中在社区的ISSUE3585 里,扫描上方二维码可阅读完整文章。
上图为第二批优化CPU 方面的具体内容,字母编号与 ISSUE3585 相对应。此前RocketMQ 使用 Fastjson 做序列化,而有一个自定义的协议性能略微优于 Fastjson,我们对其进行了深入研究和优化,最终使得 RocketMQ 在编解码上性能也得到了很大提升。
RocketMQ 构建 Queue 的程序为单线程,因此我们使用mmap buffer 代替 FileChannel,性能得到了极大提高。
上图红框中为某方法优化前后对比。
此外,我们将 Pull 通知移动到另外的线程,使其不占用线程的资源。同时,将通知自动聚批,兼顾高吞吐和低延迟,只有 TPS 较高时才会自动打开,阈值可设置,避免 Pull 通知在 Broker 和消费者之间震荡频率过高,消耗 CPU。
大消息性能不佳也是一直以来很大的困扰,经过分析我们发现 RocketMQ remoting 的 TCP 参数设置有待优化。原先的buffer 设置为固定值,当机房延迟很大时,过小的 buffer 会导致 TCP 连接的吞吐受到严重影响。因此,我们将 buffer 修改为由操作系统自动管理,以保证吞吐。
上图列出了线上实测的数据表,可以看出消费的性能提升了数倍。
在极高的 TPS 场景下,如果消息体较大,则操作系统内存分配可能会成为 RocketMQ 的瓶颈,4.X 的内核下的表现远远优于 3 .X 。极端场景下,3.X内核通常需要将参数 min_free_kbytes 调至较大值。
我们于2021年12月社区开发者会议现场演示了性能测试,对比了第二批优化前后的4.9.2版本和4.9.3 Snapshot版本(包括当时还未合并进该版本的B和K的优化)的性能。
测试时,在电脑上同时运行所有 broker、生产者、消费者。老版本TPS不到 3w,而新版本有了自动聚批的能力,TPS 可达 6 w,相比于老版本提升了一倍。
加入 Apache RocketMQ 社区
十年铸剑,Apache RocketMQ 的成长离不开全球接近 500 位开发者的积极参与贡献,相信在下个版本你就是 Apache RocketMQ 的贡献者,在社区不仅可以结识社区大牛,提升技术水平,也可以提升个人影响力,促进自身成长。
社区 5.0 版本正在进行着如火如荼的开发,另外还有接近 30 个 SIG(兴趣小组)等你加入,欢迎立志打造世界级分布式系统的同学加入社区,添加社区开发者微信:rocketmq666 即可进群,参与贡献,打造下一代消息、事件、流融合处理平台。
微信扫码添加小火箭进群
另外还可以加入钉钉群与 RocketMQ 爱好者一起广泛讨论:
钉钉扫码加群
关注「Apache RocketMQ」公众号,获取更多技术干货
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。