RocketMQ这样做,压测后性能提高30%

本文涉及的产品
性能测试 PTS,5000VUM额度
简介: RocketMQ这样做,压测后性能提高30%

从官方这边获悉,RocketMQ在4.9.1版本中对消息发送进行了大量的优化,性能提升十分显著,接下来请跟着我一起来欣赏大神们的杰作。


根据RocketMQ4.9.1的更新日志,我们从中提取到关于消息发送性能优化的【Issues:2883】,详细链接如下:具体优化点如截图所示:

88232c9910f1d4e73764b6fddb2ccec8.png

首先先尝试对上述优化点做一个简单的介绍:


  • 对WaitNotifyObject的锁进行优化(item2)
  • 移除HAService中的锁(item3)
  • 移除GroupCommitService中的锁(item4)
  • 消除HA中不必要的数组拷贝(item5)
  • 调整消息发送几个参数的默认值(item7)
  • sendMessageThreadPoolNums
  • useReentrantLockWhenPutMessage
  • flushCommitLogTimed
  • endTransactionThreadPoolNums
  • 减少琐的作用范围(item8-12)


通过阅读上述的变更,总结出优化手段主要包括如下三点:


  • 移除不必要的锁
  • 降低锁粒度(范围)
  • 修改消息发送相关参数


接下来结合源码,从中挑选具有代表性功能进行详细剖析,一起领悟Java高并发编程的魅力。


1、移除不必要的锁


本次性能优化,主要针对的是RocketMQ同步复制场景。


我们首先先来简单介绍一下RocketMQ主从同步在编程方面的技巧。


RocketMQ主节点将消息写入内存后, 如果采用同步复制,需要等待从节点成功写入后才能向消息发送客户端返回成功,在代码编写方面也极具技巧性,时许图如下图所示:

3f5193109d3f40ca4178c57cd494edb9.png

温馨提示:在RocketMQ4.7版本开始对消息发送进行了优化,同步消息发送模型引入了jdk的CompletableFuture实现消息的异步发送。


核心步骤解读:


  1. 消息发送线程调用Commitlog的aysncPutMessage方法写入消息。
  2. Commitlog调用submitReplicaRequest方法,将任务提交到GroupTransferService中,并获取一个Future,实现异步编程。值得注意的是这里需要等待,待数据成功写入从节点(内部基于CompletableFuture机制的内部线程池ForkJoin)。
  3. GroupTransferService中对提交的任务依次进行判断,判断对应的请求是否已同步到从节点。
  4. 如果已经复制到从节点,则通过Future唤醒,并将结果返回给消息发送端。

GroupTransferService代码如下图所示:


微信图片_20220607154832.jpg

为了更加方便大家理解接下来的优化点,首先再总结提炼一下GroupTransferService的设计理念:


  • 首先引入两个List结合,分别命名为读、写链表。
  • 外部调用GroupTransferService的putRequest请求,将存储在写链表中(requestWrite)。
  • GroupTransferService的run方法从requestRead链表中获取任务,判断这些任务对应的请求的数据是否成功写入到从节点。
  • 每当requestRead中没有数据可读时,两个队列进行交互,从而实现读写分离,降低锁竞争


新版本的优化点主要包括:


  • 更改putRequest的锁类型,用自旋锁替换synchronized
  • 去除doWaitTransfer方法中多余的锁


1.1 使用自旋锁替换synchronized


正入下图所示,GroupTransferService向外提供接口putRequest,用来接受外部的同步任务,需要对ArrayList加锁进行保护,往ArrayList中添加数据属于一个内存操作,操作耗时小。

677cd265fe600eac02a728cd15b75acb.png

故这里没必要采取synchronized这种synchronized,而是可以自旋锁,自旋锁的实现非常轻量级,其实现如下图所示:

e4cfddca7648d2b06f83a1861827cb73.png

整个锁的实现就只需引入一个AtomicBoolean,加锁、释放锁都是基于CAS操作,非常的轻量,并且自旋锁不会发生线程切换


1.2 去除多余的锁


“锁”的滥用是一个非常普遍的现象,多线程环境编程是一个非常复杂的交互过程,在编写代码过程中我们可能觉得自己无法预知这段代码是否会被多个线程并发执行,为了谨慎起见,就直接简单粗暴的对其进行加锁,带来的自然是性能的损耗,这里将该锁去除,我们就要结合该类的调用链条,判断是否需要加锁。


整个GroupTransferService中在多线程环境中运行需要被保护的主要是requestRead与requestWrite集合,引入的锁的目的也是确保这两个集合在多线程环境下安全访问,故我们首先应该梳理一下GroupTransferService的核心方法的运作流程:

2114dbd5ff723f7c54c3da19b9951237.png

doWaitTransfer方法操作的主要对象是requestRead链表,而且该方法只会被GroupTransferService线程调用,并且requestRead中方法会在swapRequest中被修改,但这两个方法是串行执行,而且在同一个线程中,故无需引入锁,该锁可以移除。


但由于该锁被移除,在swapRequests中进行加锁,因为requestWrite这个队列会被多个线程访问,优化后的代码如下:

f1cf808f829c30071df5e5bd68d353d2.png

从这个角度来看,其实主要是将锁的类型由synchronized替换为更加轻量的自旋锁。


2、降低锁的范围


被锁包裹的代码块是串行执行,即无法并发,在无法避免锁的情况下,降低锁的代码块,能有效提高并发度,图解如下:

1990963e228f75328c389d04780f3c8e.png


如果多个线程区访问lock1,lock2,在lock1中domSomeThing1、domSomeThing2这两个方法都必须串行执行,而多个线程同时访问lock2方法,doSomeThing1能被多个线程同时执行,只有doSomething2时才需要串行执行,其整体并发效果肯定是lock2,基于这样理论:得出一个锁使用的最佳实践:被锁包裹的代码块越少越好


在老版本中,消息写入加锁的代码块比较大,一些可以并发执行的动作也被锁包裹,例如生成offsetMsgId。

fd583e3f68f9ecd8694d8cadb416035f.jpg

新版本采用函数式编程的思路,只是定义来获取msgId的方法,在进行消息写入时并不会执行,降低锁的粒度,使得offsetMsgId的生成并行化,其编程手段之巧妙,值得我们学习。


3、调整消息发送相关的参数


  1. sendMessageThreadPoolNums

Broker端消息发送端线程池数量,该值在4.9.0版本之前默认为1,新版本调整为操作系统的CPU核数,并且不小于4。该参数的调整有利有弊。提高了消息发送的并发度,但同时会导致消息顺序的乱序,其示例图如下同步发送下不会有顺序问题,可放心修改
aa83c4ee55d3dae981fc6acb30e105d5.png

  1. 在顺序消费场景,该参数不建议修改。在实际过程中应该对RocketMQ集群进行治理,顺序消费的场景使用专门集群。


  1. useReentrantLockWhenPutMessage MQ消息写入时对内存加锁使用的锁类型,低版本之前默认为false,表示默认使用自旋锁;新版本使用ReentrantLock。自旋主要的优势是没有线程切换成本,但自旋容易造成CPU的浪费,内存写入大部分情况下是很快,但RocketMQ比较依赖页缓存,如果出现也缓存抖动,带来的CPU浪费是非常不值得,在sendMessageThreadPoolNums设置超过1之后,锁的类型使用ReentrantLock更加稳定。


  1. flushCommitLogTimed 首先我们通过观察源码了解一下该参数的含义:

df8fec146b65bbbd97c73bf9c020f250.jpg

其主要作用是控制刷盘线程阻塞等待的方式,低版本flushCommitLogTimed为false,默认使用CountDownLatch,而高版本则直接使用Thread.sleep。猜想的原因是刷盘线程比较独立,无需与其他线程进行直接的交互协作,故无需使用CountDownLatch这种专门用来线程协作的“外来和尚”。


4.endTransactionThreadPoolNums


主要用于设置事务消息线程池的大小。

6bbd90155a87888e5608dc37f6c4ddcf.png

新版本主要是可通过调整发送线程池来动态调节事务消息的值,这个大家可以根据压测结果动态调整。


相关实践学习
消息队列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
相关文章
|
2月前
|
测试技术 数据库 UED
Python 性能测试进阶之路:JMeter 与 Locust 的强强联合,解锁性能极限
【9月更文挑战第9天】在数字化时代,确保软件系统在高并发场景下的稳定性至关重要。Python 为此提供了丰富的性能测试工具,如 JMeter 和 Locust。JMeter 可模拟复杂请求场景,而 Locust 则能更灵活地模拟真实用户行为。结合两者优势,可全面评估系统性能并优化瓶颈。例如,在电商网站促销期间,通过 JMeter 模拟大量登录请求并用 Locust 模拟用户浏览和购物行为,可有效识别并解决性能问题,从而提升系统稳定性和用户体验。这种组合为性能测试开辟了新道路,助力应对复杂挑战。
107 2
|
1月前
|
消息中间件 存储 监控
说说如何解决RocketMq消息积压?为什么Kafka性能比RocketMq高?它们区别是什么?
【10月更文挑战第8天】在分布式系统中,消息队列扮演着至关重要的角色,它不仅能够解耦系统组件,还能提供异步处理、流量削峰和消息持久化等功能。在众多的消息队列产品中,RocketMQ和Kafka无疑是其中的佼佼者。本文将围绕如何解决RocketMQ消息积压、为什么Kafka性能比RocketMQ高以及它们之间的区别进行深入探讨。
74 1
|
2月前
|
缓存 Java 测试技术
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
使用JMeter对项目各个接口进行压力测试,并对前端进行动静分离优化,优化三级分类查询接口的性能
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
|
6月前
|
消息中间件 Java 测试技术
性能工具之Jmeter扩展函数及压测ActiveMQ实践
【5月更文挑战第18天】性能工具之Jmeter扩展函数及压测ActiveMQ实践
106 5
|
3月前
|
消息中间件 存储 监控
RocketMQ的性能优势?
【8月更文挑战第29天】RocketMQ的性能优势?
93 2
|
3月前
|
消息中间件 Prometheus 监控
RabbitMQ性能调优指南
【8月更文第28天】RabbitMQ 是一个非常流行的消息队列中间件,它支持多种消息协议,并且可以轻松集成到各种系统中。随着应用的扩展,确保 RabbitMQ 在高负载环境下能够高效稳定地运行变得至关重要。本文将深入探讨如何通过配置、监控以及最佳实践来优化 RabbitMQ 的性能。
440 1
|
3月前
|
消息中间件 Kafka 测试技术
【Azure 事件中心】使用Kafka的性能测试工具(kafka-producer-perf-test)测试生产者发送消息到Azure Event Hub的性能
【Azure 事件中心】使用Kafka的性能测试工具(kafka-producer-perf-test)测试生产者发送消息到Azure Event Hub的性能
|
3月前
|
监控 Java 测试技术
实战派必看!Python性能测试中,JMeter与Locust如何助力性能调优
【8月更文挑战第6天】性能优化是软件开发的关键。本文介绍JMeter与Locust两款流行性能测试工具,演示如何用于Python应用的性能调优。JMeter可模拟大量用户并发访问,支持多种协议;Locust用Python编写,易于定制用户行为并模拟高并发。根据场景选择合适工具,确保应用在高负载下的稳定运行。
131 4
|
3月前
|
测试技术 数据库 UED
Python 性能测试进阶之路:JMeter 与 Locust 的强强联合,解锁性能极限
【8月更文挑战第6天】在数字化时代,确保软件在高并发下的稳定性至关重要。Python 提供了强大的性能测试工具,如 JMeter 和 Locust。JMeter 可配置复杂请求场景,而 Locust 则以 Python 脚本灵活模拟真实用户行为。两者结合,可全面评估系统性能。例如,对电商网站进行测试时,JMeter 模拟登录请求,Locust 定义浏览和购物行为,共同揭示系统瓶颈并指导优化,从而保证稳定高效的用户体验。
101 1
|
4月前
|
存储 监控 数据可视化
性能测试:主流性能剖析工具介绍
**性能剖析**是识别应用性能瓶颈的关键,涉及指标收集、热点分析、优化建议及可视化报告。常用工具有:**JConsole**监控JVM,**VisualVM**多合一分析,**JStack**分析线程,**FlameGraph**展示CPU耗时,**SkyWalking**分布式跟踪,**Zipkin**追踪服务延迟。这些工具助力开发人员提升系统响应速度和资源效率。