rocketmq是什么语言写的_RocketMQ设计——低延迟可用性探索

疾风吹征帆,倏尔向空没。千里在俄顷,三江坐超忽。—孟浩然

低延迟与可用性

随着Java语言生态的完善,JVM性能的提高,C和C++已经不再是低延迟场景唯一的选择。本章节重点介绍RocketMQ在低延迟可用性方面的一些探索。

应用程序的性能度量标准一般从吞吐量和延迟两方面考量。吞吐量是指程序在一段时间内能处理的请求数量。延迟是指端到端的响应时间。低延迟在不同的环境下有不同的定义,比如在聊天应用中低延迟可以定义为200ms内,在交易系统中定义为10ms内。相对于吞吐量,延迟会受到很多因素的影响,如CPU、网络、内存、操作系统等。

根据Little’s law,当延迟变高时,驻留在分布式系统中的请求会剧增,导致某些节点不可用,不可用的状态甚至会扩散至其它节点,造成整个系统的服务能力丧失,这种场景又俗称雪崩。所以打造低延迟的应用程序,对提升整个分布式系统可用性有很大的裨益。

低延迟探索之路

RocketMQ作为一款消息引擎,最大的作用是异步解耦和削峰填谷。一方面,分布式应用会利用RocketMQ来进行异步解耦,应用程序可以自如地扩容和缩容。另一方面,当洪峰数据来临时,大量的消息需要堆积到RocketMQ中,后端程序可以根据自己的消费速度来进行数据的读取。所以保证RocketMQ写消息链路的低延迟至关重要。

在今年双11期间,天猫发布了红包火山的新玩法。该游戏对延迟非常敏感,只能容忍50ms内的延迟,在压测初期RocketMQ写消息出现了大量50~500ms的延迟,导致了在红包喷发的高峰出现大量的失败,严重影响前端业务。下图为压测红包集群在压测时写消息延迟热力图统计。

caf4baf4543f34e7f70720c17375f670.png

作为一款纯Java语言开发的消息引擎,RocketMQ自主研发的存储组件,依赖Page Cache进行加速和堆积,意味着它的性能会受到JVM、GC、内核、Linux内存管理机制、文件IO等因素的影响。如下图所示,一条消息从客户端发送出,到最终落盘持久化,每个环节都有产生延迟的风险。通过对线上数据的观察,RocketMQ写消息链路存在偶发的高达数秒的延迟。

7169fdf5b6d837c42acff4f9a7d69797.png

1 JVM停顿

JVM(Java虚拟机)在运行过程中会产生很多停顿,常见的有GC、JIT、取消偏向锁(RevokeBias)、RedefineClasses(AOP)等。对应用程序影响最大的则是GC停顿。RocketMQ尽量避免Full GC,但Minor GC带来的停顿是难以避免的。针对GC调优是一个很伽利略的问题,需要通过大量的测试来帮助应用程序调整GC参数,比如可以通过调整堆大小,GC的时机,优化数据结构等手段进行调优。

对于其它JVM停顿,可以通过-XX:+PrintGCApplicationStoppedTime将JVM停顿时间输出到GC日志中。通过-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1输出具体的停顿原因,并进行针对性的优化。比如在RocketMQ中发现取RevokeBias产生了大量的停顿,通过-XX:-UseBiasedLocking关闭了偏向锁特性。

另外,GC日志的输出会发生文件IO,有时候也会造成不必要的停顿,可以将GC日志输出到tmpfs(内存文件系统)中,但tmpfs会消耗内存,为了避免内存被浪费可以使用-XX:+UseGCLogFileRotation滚动GC日志。

除了GC日志会产生文件IO,JVM会将jstat命令需要的一些统计数据输出到/tmp(hsperfdata)目录下,可通过-XX:+PerfDisableSharedMem关闭该特性,并使用JMX来代替jstat。

2 锁——同步的“利”器

作为一种临界区的保护机制,锁被广泛用于多线程应用程序的开发中。但锁是一把双刃剑,过多或不正确的使用锁会导致多线程应用的性能下降。

Java中的锁默认采用的是非公平锁,加锁时不考虑排队问题,直接尝试获取锁,若获取失败自动进行排队。非公平锁会导致线程等待时间过长,延迟变高。倘若采取公平锁,又会对应用带来较大性能损失。

另一方面,同步会引起上下文切换,这会带来一定的开销。上下文切换一般是微秒级,但当线程数过多,竞争压力大时,会产生数十毫秒级别的开销。可通过LockSupport.park来模拟产生上下文切换进行测试。

为了避免锁带来的延迟,利用CAS原语将RocketMQ核心链路无锁化,在降低延迟的同时显著提高吞吐量。

3 内存——没那么快

受限于Linux的内存管理机制,应用程序访问内存时有时候会产生高延迟。Linux中内存主要有匿名内存和Page Cache两种。

Linux会用尽可能多的内存来做缓存,大多数情形下,服务器可用内存都较少。可用内存较少时,应用程序申请或者访问新的内存页会引发内存回收,当后台内存回收的速度不及分配内存的速度时,会进入直接回收(Direct Reclaim),应用程序会自旋等待内存回收完毕,产生巨大的延迟,如下图所示。

28b653822825fef7aaee5760e8bed36c.png

另一方面,内核也会回收匿名内存页,匿名内存页被换出后下一次访问会产生文件IO,导致延迟,如下图所示。

9417bfb03da6d9e622cc87edb41e444e.png

上述两种情况产生的延迟可以通过内核参数(vm.extra_free_kbytes和vm.swappiness)调优加以避免。

Linux对内存的管理一般是以页为单位,一页一般为4k大小,当在同一页内存上产生读写竞争时,会产生延迟,对于这种情况,需要应用程序自行协调内存的访问加以避免。

4 Page Cache——利与弊

Page Cache是文件的缓存,用于加速对文件的读写,它为RocketMQ提供了更强大的堆积能力。RocketMQ将数据文件映射到内存中,写消息的时候首先写入Page Cache,并通过异步刷盘的模式将消息持久化(同时也支持同步刷盘),消息可以直接从Page Cache中读取,这也是业界分布式存储产品通常采用的模式,如下图所示:

b4e0f714dabf3772c825cdc26a1e73a2.png

该模式大多数情况读写速度都比较迅速,但当遇到操作系统进行脏页回写,内存回收,内存换入换出等情形时,会产生较大的读写延迟,造成存储引擎偶发的高延迟。

针对这种现象,RocketMQ采用了多种优化技术,比如内存预分配,文件预热,mlock系统调用,读写分离等,来保证利用Page Cache优点的同时,消除其带来的延迟。

优化成果

RocketMQ通过对上述情况的优化,成功消除了写消息高延迟的情形,并通过了今年双11的考验。优化后写消息耗时热力图如下图所示。

f74d7e32d4f9395ea8a92b8ca165d275.png

优化后RocketMQ写消息延迟99.995%在1ms内,100%在100ms内,如下图所示。

d61c5433944d571ea88e1c920e67243d.png

转自:http://jm.taobao.org/2017/01/26/20170126/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值