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

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

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

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

接下来我们逐一来看看其优化点,并简单加以分析。

通过阅读相关的变更,优化手段主要包括:

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

接下来根据上述手段,从中挑选具有代表性功能进行详细剖析,一起领悟Java高并发编程。

1、移除不必要的锁

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

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

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

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

核心步骤解读:

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

GroupTransferService代码如下图所示:



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

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

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

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

1.1 使用自旋锁替换synchronized

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

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

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

1.2 去除多余的锁

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

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

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

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

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

2、降低锁的范围

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

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

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

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

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

  1. sendMessageThreadPoolNums

    Broker端消息发送端线程池数量,该值在4.9.0版本之前默认为1,新版本调整为操作系统的CPU核数,并且不小于4。

  2. useReentrantLockWhenPutMessage

    MQ消息写入时对内存加锁使用的锁类型,低版本之前默认为false,表示默认使用自旋锁;新版本使用ReentrantLock。

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

  3. flushCommitLogTimed

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

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

  4. endTransactionThreadPoolNums

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



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

文章首发:https://www.codingw.net/posts/fbea8b3.html

一键三连(关注、点赞、留言)是对我最大的鼓励

掌握一到两门java主流中间件,是敲开BAT等大厂必备的技能,送给大家一个Java中间件学习路线,助力大家早日进入互联网大厂。

Java进阶之梯,成长路线与学习资料,助力突破中间件领域

最后分享笔者一个硬核的RocketMQ电子书,您将获得千亿级消息流转的运维经验。



获取方式:RocketMQ电子书

RocketMQ这样做,压测后性能提高30%的更多相关文章

  1. ESRally压测ElasticSearch性能 CentOS 7.5 安装 Python3.7

    1,CentOS 7.5 安装 Python3.7 1.安装开发者工具 yum -y groupinstall "Development Tools"2.安装Python编译依赖包 ...

  2. 压测 swoole_websocket_server 性能

    概述 这是关于 Swoole 入门学习的第十篇文章:压测 swoole_websocket_server 性能. 第九篇:Swoole Redis 连接池的实现 第八篇:Swoole MySQL 连接 ...

  3. ab命令做压测测试

    1. 背景:互联网发达的今天,大大小小的网站如雨后春笋,不断出现,但是想要做出一个网站很简单,但是想要做好一个网站,非常非常难,首先:网站做好之后的功能怎么样这都是次要的,主要的是你的网站能承受怎么样 ...

  4. 实战jmeter入门压测接口性能

    什么是Jmeter? 是Apache组织开发的基于Java的压力测试工具. 准备工作: 一.安装配置好环境及压测工具 Jmeter下载地址:http://mirrors.tuna.tsinghua.e ...

  5. JMeter接口压测和性能监测

    JMeter接口压力测试总结 一.安装JMeter 1.     在客户端机器上安装JMeter压测工具,我这里安装的版本是apache-jmeter-5.2.1,由于JMeter是JAVA语言开发的 ...

  6. Jmeter让压测随时做起来(转载)

    为什么要压测 这个问题问的其实挺没有必要的,做开发的同学应该都很清楚,压测的必要性,压力测试主要目的就是让我们在上线前能够了解到我们系统的承载能力,和当前.未来系统压力的提升情况,能够评估出当前系统的 ...

  7. 6. 堪比JMeter的.Net压测工具 - Crank 实战篇 - 收集诊断跟踪信息与如何分析瓶颈

    目录 堪比JMeter的.Net压测工具 - Crank 入门篇 堪比JMeter的.Net压测工具 - Crank 进阶篇 - 认识yml 堪比JMeter的.Net压测工具 - Crank 进阶篇 ...

  8. [软件测试]网站压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  9. elasticsearch系列(二) esrally压测

    环境准备 linux centOS(工作环境) python3.4及以上 pip3 JDK8 git1.9及以上 gradle2.13级以上 准备过程中的坑 这些环境准备没什么太大问题,都是wget下 ...

随机推荐

  1. 鸿蒙轻内核M核的故障管家:Fault异常处理

    摘要:本文先简单介绍下Fault异常类型,向量表及其代码,异常处理C语言程序,然后详细分析下异常处理汇编函数实现代码. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十八 Fault异常处理& ...

  2. WPF进阶技巧和实战08-依赖属性与绑定03

    数据提供者 在大多数的代码中,都是通过设置元素的DataContext属性或者列表控件的ItemsSource属性,从而提供顶级的数据源.当数据对象是通过另一个类构造时,可以有其他选择. 一种是作为窗 ...

  3. 【转】PLI是什么以及怎么用

    programmable language interface 这里就说给verilog用的一些系统函数,还是无双大大的帖子 首先介绍了怎么让你自己写的pli系统函数在ncverilog里面可以成功调 ...

  4. Oracle 扩容表空间

    system用户登陆oracle https://blog.csdn.net/zyingpei/article/details/88870693 首先查看表空间对应的数据文件位置以及大小 select ...

  5. 小米多模网关接入Home Assistant ZNDMWG03LM

    一.小米zigbee网关使用 先下载米家app,打开手机蓝牙,登陆点"我的"界面,将网关设备插上电源,橙灯闪烁,点击蓝牙网关等待弹窗提示连接,选择连接路由器(需2.4GHz),输入 ...

  6. 安装RedHat和Centos后做的15件事情

    由于之前的Centos 7不支持无线网络连接,我尝试着将内核升级至4.8还是无效,遂决定换回RedHat 7,目前系统已经安装好,版本是Red Hat Enterprise Linux 7.3,下面是 ...

  7. Linux常用命令和快捷键整理:(1)常用命令

    前言: Linux常用快捷键和基本命令整理,先上思维导图: 1.ls命令 就是list的缩写,通过ls 命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录.文件夹.文件权限) ...

  8. loadrunner奇怪问题解决:TPS中有Action_Transaction 和 vuser_init_Transaction

    TPS图里多出两条曲线:Action_Transaction 和 vuser_init_Transaction 如下图: 解决方法: Runtime-Settings-Miscellaneous--A ...

  9. Part 38 AngularJS cancel route change

    n this video we will discuss, how to cancel route change in Angular with an example. This is extreme ...

  10. MySQL基础语句(修改)

    ①INSERT INSERT INTO students (class_id, name, gender, score) VALUES (2, '大牛', 'M', 80); 向students表插入 ...