【亿级数据专题】「分布式消息引擎」 盘点本年度我们探索服务的低延迟可用性机制方案实现

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

背景介绍

在充满挑战的2023年度,我们不可避免地面对了一系列棘手的问题,例如响应速度缓慢、系统陷入雪崩状态、用户遭受不佳的体验以及交易量的下滑。这些问题的出现,严重影响了我们的业务运行和用户满意度,为了应对这些问题,我们所在团队进行了大量的研究和实践,提出了低延迟高可用的解决方案,并在分布式存储领域广泛应用。

专题简介

秉持着解决问题和攻克难题的精神,我决定开展一个【亿级数据专题】流量的系列探索和技术分享活动。我们希望通过这个专题系列,汇集各界的分享者和专家,共同探讨如何应对亿级数据所带来的难题和问题。

通过对有限资源的规划,团队还提出了分级的容量保障策略,通过限流、降级和熔断技术等手段,保证了重点业务的高吞吐,同时,在一些对可靠性和可用性要求极高的场景下,团队还专门推出了基于多副本机制的高可用解决方案,能够动态识别机器宕机、机房断网等灾难场景,并实现主备切换,提高了消息存储的可靠性和整个集群的高可用性。

本文方向

本文主要面向通过对一个消息引擎发展历程的回顾,介绍了在高峰期所面临的低延迟挑战,并详细探讨了低延迟高可用解决方案和基于多副本机制的高可用解决方案的特点和优势。这些创新的举措为保证系统的稳定运行和可靠性提供了有效的支持。

消息中间件发展史

经过多代的演进,中间件消息引擎在发展至今已经取得了巨大的进步。

  • 第一阶段:采用了推模式,并且使用关系型数据库进行数据存储。在这种模式下,消息具有低延迟的特性,在高频交易场景中尤其广泛应用。

  • 第二阶段:采用了拉模式+推模式的双模式消息存储系统,例如,Kafka的吞吐性能。然而考虑到应用场景,特别是对交易链路等高可靠性的要求,消息引擎更加注重稳定可靠性,而非仅仅追求高吞吐量。采用了长连接拉模式,在消息实时传输方面与推模式不相上下。

  • 第三阶段:采用了流式消息处理的消息存储系统,它能够实时地接收、存储和处理大量的数据流。这种消息存储系统可以快速地处理高吞吐量的消息,并保证数据的顺序性和可靠性。

通过流式消息处理,系统能够实时地对数据流进行分析、计算和转换,从而实现实时监控、实时决策和实时反馈。

低延迟可用性探索

随着Java语言生态系统的完善和JVM性能的不断提升,C和C++不再是低延迟场景下的唯一选择。在这种背景下,接下来将重点介绍RocketMQ在低延迟和可用性方面的一些探索和创新。

截至至今,我们公司的核心文档业务依然以高性能、低延迟的消息引擎RocketMQ为主。

低延迟与可用性

应用程序的性能指标通常从吞吐量 和延迟 两个方面进行评估。

  • 低延迟:不同应用场景对于低延迟的要求有所不同,例如在聊天应用中,低延迟可以定义为200毫秒以内,而在交易系统中,可能要求达到10毫秒以内的延迟。

  • 吞吐量:延迟受多个因素的影响,包括CPU性能、网络速度、内存使用以及操作系统的优化等。

吞吐量和Little's Law定理的关系

Little's Law是由约翰·D·利特尔(John D. Little)提出的一个基本定理,它描述了一个稳定系统中平均流动时间与系统中平均存储量之间的关系,这个定理对于评估和优化系统的性能非常有用。

该定理可以用简洁的公式来表达:

ini 复制代码
L = λW
  • L:系统中平均存储量(或者说队列长度)
  • λ:单位时间内进入系统的平均请求量(也称为流入率)
  • W:一个请求在系统中的平均逗留时间(也称逗留时间)。

Little's Law告诉我们,当进入系统的流量(λ)和平均逗留时间(W)保持不变时,系统中的平均存储量(L)也会保持不变。

注意:当延迟变高时,驻留在分布式系统中的请求会剧增( L变大),最终导致某些节点堆积假死不可用,不可用的状态甚至会扩散至其它节点,造成整个系统的服务能力丧失,这种场景又俗称雪崩。

低延迟探索之路

为了实现低延迟的消息写入链路,RocketMQ采取了一系列的优化RocketMQ作为一款消息引擎,其最重要的作用之一是实现异步解耦和平滑处理系统的峰值数据。

RocketMQ现异步解耦

通过RocketMQ,分布式应用能够实现异步解耦,从而实现应用程序的自动扩容和缩容。同时,当高峰数据到来时,大量的消息可以积攒在RocketMQ中,后端程序可以根据自身的消费速度来逐步读取数据。因此,保证RocketMQ在写消息链路上的低延迟至关重要。

降低写入消息的延迟

通过降低写入消息的延迟,我们可以提高消息的实时性和可靠性。

为了实现这一目标,可以采取多种方法,例如优化消息的处理逻辑和网络通信,调整消息的存储和传输机制等。如,网络RocketMQ作为一款消息引擎,具有异步解耦和削峰填谷的重要功能。

RocketMQ优化延迟调整

对延迟非常敏感,只能容忍 50ms 内的延迟,在压测初期RocketMQ写消息出现了大量50~500ms 的延迟,导致了高峰出现大量的失败,严重影响前端业务。

延迟问题分析

RocketMQ是一款完全由Java语言开发的消息引擎。它使用了自主研发的存储组件,并通过Page Cache来进行加速和存储,因此它的性能会受到多个因素的影响,包括JVM、GC、操作系统的内核、Linux内存管理机制以及文件IO等。

下面的图示展示了一条消息从客户端发送到最终持久化存储的过程中,每个环节都可能产生延迟。 然而,通过对线上数据的观察,发现RocketMQ在写消息的过程中存在偶发的延迟问题,这些延迟可能高达数秒之久。

为了解决这个问题,可以采取以下优化措施:

JVM和GC调优

Java虚拟机在运行过程中会产生多种停顿,其中包括GC(垃圾回收)、取消偏向锁(RevokeBias)和动态类重定义(RedefineClasses,如AOP)。对应用程序影响最大的是GC停顿。在RocketMQ中,尽可能避免Full GC的触发,但是Minor GC引起的停顿是难以避免的。

通过大量的测试来帮助应用程序调整GC参数。以下是一些优化策略方案:

关闭偏向锁(RevokeBias )

在 RocketMQ 中发现取 RevokeBias 产生了大量的停顿,通过-XX:-UseBiasedLocking关闭了偏向锁特性,此外还可以打印GC停顿时间和停顿原因,从而分析其他的因素。

  • 打印GC停顿时间 :可以通过-XX:+PrintGCApplicationStoppedTime将 JVM 停顿时间输出到 GC 日志中

  • 打印GC停顿原因 :通过-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1输出具体的停顿原因,并进行针对性的优化。

减少日志IO的控制和日志压缩

GC日志的输出会涉及文件IO操作,这可能导致不必要的停顿。

优化方法是将GC日志输出到tmpfs(内存文件系统)中,但使用tmpfs会消耗额外的内存。为了避免内存的浪费,可以考虑使用-XX:+UseGCLogFileRotation选项来实现GC日志的滚动。

  • 设置-XX:+UseGCLogFileRotation参数,GC日志将自动进行滚动,以避免日志文件过大而影响性能。
  • 设置-XX:GCLogFileSize参数,指定单个GC日志文件的大小。
  • 设置-XX:NumberOfGCLogFiles参数,指定保存的日志文件数量。

这样做的好处是可以减小单个GC日志文件的大小,降低文件IO的开销,同时便于管理和查阅GC日志。

关闭jstat的特性和功能

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

PageCache以及内存优化

受限于Linux的内存管理机制,应用程序在访问内存时有时会出现高延迟的情况。在Linux中,内存主要可以分为两种类型,即匿名内存和Page Cache。

内存申请和分配流程

为了提高性能和效率,系统会尽可能多地使用可用内存来进行缓存。然而,在大多数情况下,服务器的可用内存相对较少。当可用内存较少时,应用程序申请或访问新的内存页可能导致内存回收的发生。

当后台内存回收的速度跟不上内存分配的速度时,就会发生直接回收(Direct Reclaim)的情况。这时,应用程序会被迫等待内存回收完成,从而导致巨大的延迟,如下图所示: 另一方面,内核也会回收匿名内存页,匿名内存页被换出后下一次访问会产生文件 IO,导致延迟,如下图所示。 上述两种情况产生的延迟可以通过内核参数调优加以避免。

  • vm.extra_free_kbytes :这是一个用于设置系统中额外可用的空闲内存大小的参数。当系统内存接近饱和状态时,操作系统会通过回收内存页面来释放内存空间。然而,为了确保系统的稳定性和性能,通常会保留一定量的空闲内存。
    • 增加vm.extra_free_kbytes的值可以增加系统的可用空闲内存,从而降低内存压力,但也会占用更多的物理内存资源。
  • vm.swappiness :这是一个用于设置内存页交换行为的参数。Linux操作系统使用页面交换(paging)机制来处理内存不足的情况。
    • 当可用内存低于一定阈值时,操作系统会将一部分内存页存储到磁盘的交换分区中,从而释放出物理内存。
    • vm.swappiness参数可以调整系统对交换行为的偏好程度。它的取值范围是0到100,其中0表示不进行内存页交换,而100表示尽量进行内存页交换。
Page Cache的利与弊

Page Cache是一种用于加速文件读写的缓存机制,在RocketMQ中发挥着重要的作用,使其具备强大的数据堆积能力。 RocketMQ通过将数据文件映射到内存中的方式,将写入的消息首先存储在Page Cache中,并通过异步刷盘模式将消息持久化到磁盘(同时也支持同步刷盘)。

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

总结归纳

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

必学知识点:Little's Law

Little's Law在应用于各种领域和场景时都具有广泛的适用性,包括计算机网络、排队系统、供应链管理等。它可以帮助我们理解系统中的请求流动行为,并且可以用来优化系统的吞吐量、运营效率和服务质量。

采用-XX:+UseGCLogFileRotation选项,并合理设置相关参数,可以优化GC日志的输出和管理,降低对文件IO的影响,同时避免内存浪费和磁盘空间的占用。

相关推荐
云和数据.ChenGuang25 分钟前
用 Claude Code 重构遗留系统:从评估到落地的完整指南
java·人工智能·spring boot·后端·重构·springai
DBA_G39 分钟前
GBase 8c分布式数据库客户端工具 gsql
数据库·分布式
chunyublog40 分钟前
Spark 词频统计练习
大数据·分布式·spark
豆角焖肉1 小时前
SpringBoot 集成腾讯 AI 人脸识别
人工智能·spring boot·后端·人脸识别·腾讯 ai·base64 图片
付威20231 小时前
如果你想拥有自己的 Agent,为什么值得从 rpi 开始
后端
可乐ea1 小时前
2000 个智能体把 Agent 重写成 Rust:编排流水线拆解
开发语言·后端·rust·多智能体协作·ai编程助手·智能体编排·rust重写
用户72757483196501 小时前
02-变量、常量和字符串:len("Hello, 世界") 为什么不是 9?
后端
SamDeepThinking2 小时前
CyclicBarrier的底层实现
java·后端·面试
god_cvz2 小时前
第二章:6、对话记忆与引用标注
后端·ai编程
拖孩2 小时前
全程 AI 写的小程序,刚接上会员虚拟支付,上线第二天就有人真金白银买单了|双签名、定价、iOS 的坑我全踩了一遍
前端·后端·微信小程序