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

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

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

在这里插入图片描述

背景介绍

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

专题简介

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

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

本文方向

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

消息中间件发展史

经过多代的演进,中间件消息引擎在发展至今已经取得了巨大的进步。
在这里插入图片描述

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

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

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

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

低延迟可用性探索

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

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

低延迟与可用性

应用程序的性能指标通常从吞吐量延迟两个方面进行评估。
在这里插入图片描述

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

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

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

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

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

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的影响,同时避免内存浪费和磁盘空间的占用。

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
1月前
|
存储 缓存 算法
分布式锁服务深度解析:以Apache Flink的Checkpointing机制为例
【10月更文挑战第7天】在分布式系统中,多个进程或节点可能需要同时访问和操作共享资源。为了确保数据的一致性和系统的稳定性,我们需要一种机制来协调这些进程或节点的访问,避免并发冲突和竞态条件。分布式锁服务正是为此而生的一种解决方案。它通过在网络环境中实现锁机制,确保同一时间只有一个进程或节点能够访问和操作共享资源。
63 3
|
1月前
|
消息中间件 存储 监控
消息队列系统中的确认机制在分布式系统中如何实现
消息队列系统中的确认机制在分布式系统中如何实现
|
1月前
|
消息中间件 存储 监控
【10月更文挑战第2天】消息队列系统中的确认机制在分布式系统中如何实现
【10月更文挑战第2天】消息队列系统中的确认机制在分布式系统中如何实现
|
1月前
|
存储 数据采集 分布式计算
Hadoop-17 Flume 介绍与环境配置 实机云服务器测试 分布式日志信息收集 海量数据 实时采集引擎 Source Channel Sink 串行复制负载均衡
Hadoop-17 Flume 介绍与环境配置 实机云服务器测试 分布式日志信息收集 海量数据 实时采集引擎 Source Channel Sink 串行复制负载均衡
44 1
|
29天前
|
消息中间件 存储 监控
消息队列系统中的确认机制在分布式系统中如何实现?
消息队列系统中的确认机制在分布式系统中如何实现?
|
2月前
|
数据采集 分布式计算 MaxCompute
MaxCompute 分布式计算框架 MaxFrame 服务正式商业化公告
MaxCompute 分布式计算框架 MaxFrame 服务于北京时间2024年09月27日正式商业化!
83 3
|
1月前
|
存储 缓存 数据处理
深度解析:Hologres分布式存储引擎设计原理及其优化策略
【10月更文挑战第9天】在大数据时代,数据的规模和复杂性不断增加,这对数据库系统提出了更高的要求。传统的单机数据库难以应对海量数据处理的需求,而分布式数据库通过水平扩展提供了更好的解决方案。阿里云推出的Hologres是一个实时交互式分析服务,它结合了OLAP(在线分析处理)与OLTP(在线事务处理)的优势,能够在大规模数据集上提供低延迟的数据查询能力。本文将深入探讨Hologres分布式存储引擎的设计原理,并介绍一些关键的优化策略。
95 0
|
1月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
3月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
108 2
基于Redis的高可用分布式锁——RedLock
|
7天前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
40 16

热门文章

最新文章