高并发写利器-组提交,我的Spring组件实战
高并发写优化理论
对于高并发的读QPS优化手段较多,最经济简单的方式是上缓存。但是对于高并发写TPS该如何提升?业界常用的有分库分表、异步写入等技术手段。但是分库分表对于业务的改造十分巨大,涉及迁移数据的麻烦工作,不会作为常用的优化手段。异步写入到时经常在实际工作中使用,但是也不适合所有场景,特别对于带有事务的写入请求,带事务的写入请求通常是需要同步告知用户处理结果,所以不适用异步处理。
我们都知道批处理会比单条处理快很多,只需要发起一次网络请求,在网络层面节省了N次TCP连接获取和发送数据的步骤。实际我测试过,通过shark抓包,发现建立一条TCP连接可能需要耗费10ms~50ms左右。如果是跨洲际的TCP连接更久,可能耗费几百毫秒。单是节省的多次TCP连接就能节省不少时间,其次还有程序代码的循环执行时间。所以将多个写请求聚合成一个合适大小的批量写请求,一次性将数据发送给服务器进行批量写入是最高效的。
MySQL的组提交原理
在MySQL层面,为了保证事务的可靠性和数据同步给备节点、从节点的可靠性。通常会开启双一设置。在双一设置开启后,就会在事务提交前将redo log、binlog落盘,事务才返回成功,这就是WAL机制。
sync_binlog=1
innodb_flush_log_at_trx_commit=1
我们知道由于WAL机制,写入请求在修改了数据页后不会立即刷回磁盘,而是通过记录rodo log和binlog保证事务的持久性和同步给从节点。写rodo log和binlog就是顺序写入的,涉及磁盘的顺序写机制。磁盘顺序写会比随机写快很多。MySQL为了进一步提升多个事务在高并发下写入binlog的性能,采用了“组提交”的概念。顾名思义就是将多个事务在单位时间内聚集起来,一起写入磁盘,就变成了多事务的批量顺序写入,性能高很多。
这里简单介绍组提交。首先MySQL有2个参数控制组提交的等待时间和组大小。
binlog_group_commit_sync_delay=N:在等待N μs后,开始事务刷盘(图中Sync binlog)
binlog_group_commit_sync_no_delay_count=N:如果队列中的事务数达到N个,就忽视binlog_group_commit_sync_delay的设置,直接开始刷盘
解释下这张图。首先在第一步就已经将redo log刷到磁盘了,接下来就是将多个事务聚合在一个组调用write函数写入OS的缓冲。第一个到达的事务就会开启一个新组,等待N个事务到达或者等待N微秒之后主动提交。假设事务T1到达并开启新组1,等待T2来到加入组1,等待时间满后T1主动调用write函数将T1、T2事务都写入OS缓冲。此时T1、T2组成的组1进入第二个阶段,准备调用flush函数将缓冲区的数据刷入磁盘。组1在第二阶段继续等待新事务加入,此时有新组到达就会将组2和组1合并新组,再调用flush函数将组1、组2数据刷入磁盘。整个过程是批量+顺序写入磁盘,是很高效的。
我的组提交Spring组件
我把这个组提交管理器的组件放到我GitHub上了,大家觉得不错的请Star,或觉得有优化空间的请提出mr,有错误的请斧正。
GitHub-组提交管理器
我们基于以上的理论分析,可以得出如果我们在高并发写入的时候能够模仿MySQL的组提交,实现一个主动等待和被动唤醒提交的组提交机制,将多个写入请求合成一个请求发送给MySQL就能提高写入性能。
总结MySQL的组提交机制原理:
- 第一个到达的线程开启新组作为本组Leader领导本组的数据提交
- Leader等待指定X毫秒时间,时间到后主动发起提交
- 第K个线程到达,若发现本组负载满了唤醒Leader进行本组提交
- 组与组之间互不阻塞,单位时间内可能有多个组并发提交
基于以上原理,我设计了两个类:GroupManager组管理器、GroupCommit组提交对象。GroupManager负责接收外部线程提交的数据,然后放到当前组里。并且实现整个组提交的流程。GroupCommit是一个组的具象化对象,提供一个组的入队,提交数据,挂起等待,唤醒Leader等基础方法,给GroupManager调用以实现组提交机制。
为了避免高并发时多线程竞相进入组内,导致组错乱,使用了两把锁解决。大部分线程都会被挡在第一关,每次只会放一个线程进到临界区尝试入组。入组之前要先获得当前组的锁,为什么要第二把锁?因为Leader会主动醒来提交本组的数据队列,所以提交时要确保所有资源都是排他的,需要组内锁来保证。入组的线程抢到组内锁之后就代表可以安全入组,此时有三种情况:
- 如果此时入组前发现组已经满了就开一个新组自己当Leader并唤醒当前组的Leader让它赶快提交
- 如果入组后发现组满了,唤醒当前组Leader让它赶快提交,自己则挂起等待提交后唤醒
- 入组后发现还未满,挂起自己等待唤醒
线程在获取到组内锁后都会立即释放GroupManager的锁,目的是让后续线程如果发现当前组满了,就立即开新组提交,提高效率。
系统架构
因为我们工作中大多数使用的是Tomcat容器,目前Tomcat的IO处理模型是Reactor+线程池的模式。
在整个系统架构层面,组提交影响性能的有两个参数:组大小和等待时间。组大小就是在组内挂起等待的线程数,等待时间是Leader主动等待的毫秒数。组大小直接影响到剩余可工作的线程数,Tomcat线程数量默认200,通常我们根据业务场景和硬件资源调整,线程数量也就几百左右。如果组大小太大同时等待时间太久!!直接把Tomcat所有线程都挂起了这时服务器就假死了,所以对组大小的设置建议通过压测来确定,按照下面的压测经验一般建议设置为Tomcat线程数量的1/4~1/2。这样最大1/2能确保还有一半线程可以服务其它请求。
等待时间,因为这个参数会导致接口RT上升,建议设置在5ms~20ms之间。我们生产MySQL的组提交等待时间设置500微秒,是很短的。我经过反复压测和调参发现,纯MySQL插入操作,等待时间5ms左右就合适了。
总结起来,组大小和等待时间需要根据业务类型和Tomcat线程数量和CPU数量,经过测试来决定一个合适的参数,没有通用的方法论能决定。
在整个系统架构层面,负载均衡器和服务器Pod,Tomcat线程池和多个组提交的关系。
压测报告
环境介绍
- Mac OS M2 10核16G,SSD
- MySQL 8.0
- JDK8u221
- SpringBoot,Tomcat线程池400
- Druid数据库连接池 40连接数
- Jmeter 5.3,700线程并发,循环1000,共70万请求
- JVM参数设置
-XX:-ClassUnloadingWithConcurrentMark -Xms4g -Xmx4g -Xmn3g -XX:G1HeapRegionSize=4m -XX:InitiatingHeapOccupancyPercent=30 -XX:MaxGCPauseMillis=200 -XX:MaxMetaspaceSize=268435456 -XX:MetaspaceSize=268435456 -XX:ParallelGCThreads=10 -XX:+ParallelRefProcEnabled -XX:-ReduceInitialCardMarks -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC
MySQL没有经过调优都是默认的参数。MySQL和应用服务还有Jmeter都是在Mac上运行的。对比两种测试用例:1.使用组提交组件 2.单条数据写入。
@PostMapping("/submit")public Boolean submit() {long tid = Thread.currentThread().getId();log.info("threadId={}", tid);OrderInfo orderInfo = new OrderInfo();orderInfo.setOrderNo(UUID.randomUUID().toString());orderInfo.setAddressId(123321123321123L);orderInfo.setMerchantId(123321123321123L);orderInfo.setUserId(123321123321123L);orderInfo.setOrderAmount(BigDecimal.valueOf(123123L));return groupManager.queueGroup(orderInfo);}@PostMapping("/submit2")public Boolean submit2() {OrderInfo orderInfo = new OrderInfo();orderInfo.setOrderNo(UUID.randomUUID().toString());orderInfo.setAddressId(123321123321123L);orderInfo.setMerchantId(123321123321123L);orderInfo.setUserId(123321123321123L);orderInfo.setOrderAmount(BigDecimal.valueOf(123123L));return orderInfoService.save(orderInfo);}
经过反复实验以及调整组提交的组大小、等待时间参数,得出组大小200,等待时间5ms,得出的TPS是比较好的。TPS达到近8800。接口错误率几乎没有
单提交(每次请求提交一次)所有配置和环境一致的情况下。并发700,循环1000次,70万请求。TPS在5200。错误率0
可以看出组提交比单提交TPS高出68%左右,优化比较明显。如果能针对组大小和等待时间继续调整优化,可能TPS会更高。RT上平均时间比但提交快了1倍,但是P99、P95、P90都比单提交要慢1倍。
相关文章:

高并发写利器-组提交,我的Spring组件实战
高并发写优化理论 对于高并发的读QPS优化手段较多,最经济简单的方式是上缓存。但是对于高并发写TPS该如何提升?业界常用的有分库分表、异步写入等技术手段。但是分库分表对于业务的改造十分巨大,涉及迁移数据的麻烦工作,不会作为…...

音视频入门基础:MPEG2-PS专题(4)——FFmpeg源码中,判断某文件是否为PS文件的实现
一、引言 通过FFmpeg命令: ./ffmpeg -i XXX.ps 可以判断出某个文件是否为PS文件: 所以FFmpeg是怎样判断出某个文件是否为PS文件呢?它内部其实是通过mpegps_probe函数来判断的。从《FFmpeg源码:av_probe_input_format3函数和AVI…...

如何使用OpenCV进行抓图-多线程
前言 需求: 1、如何使用OpenCV捕抓Windows电脑上USB摄像头的流、 2、采用多线程 3、获知当前摄像头的帧率。 这个需求,之前就有做了,但是由于出现了一个问题,人家摄像头的帧率目前都可以达到60帧/s 了,而我的程序…...

电子应用设计方案86:智能 AI背景墙系统设计
智能 AI 背景墙系统设计 一、引言 智能 AI 背景墙系统旨在为用户创造一个动态、个性化且具有交互性的空间装饰体验,通过融合先进的技术和创意设计,提升室内环境的美观度和功能性。 二、系统概述 1. 系统目标 - 提供多种主题和风格的背景墙显示效果&…...

【《python爬虫入门教程11--重剑无峰168》】
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 【《python爬虫入门教程11--selenium的安装与使用》】 前言selenium就是一个可以实现python自动化的模块 一、Chrome的版本查找?-- 如果用edge也是类似的1.chrome…...
.net core 线程锁,互斥锁,自旋锁,混合锁
线程锁、互斥锁、自旋锁和混合锁是多线程编程中的重要概念,它们用于控制对共享资源的访问,避免数据竞争和不一致性。每种锁有其特定的适用场景和特点。我们来逐一解释它们,并进行比较。 1. 线程锁(Thread Lock) 线程…...

【DevOps】Jenkins项目发布
Jenkins项目发布 文章目录 Jenkins项目发布前言资源列表基础环境一、Jenkins发布静态网站1.1、项目介绍1.2、部署Web1.3、准备gitlab1.4、配置gitlab1.5、创建项目1.6、推送代码 二、Jenkins中创建gitlab凭据2.1、创建凭据2.2、在Jenkins中添加远程主机2.3、获取gitlab项目的UR…...

C# OpenCV机器视觉:霍夫变换
在一个阳光灿烂得近乎放肆的午后,阿强的实验室就像被施了魔法的科学城堡,到处闪耀着神秘的科技光芒。阿强呢,像个即将踏上惊险征程的探险家,一屁股坐在那堆满奇奇怪怪设备的桌前,眼神中透露出按捺不住的兴奋劲儿&#…...
Kraft模式安装Kafka(含常规、容器两种安装方式)
一、#创作灵感# 公司使用Kafka的软件项目较多,故写技术笔记巩固知识要点 二、软件环境 - Kafka 3.9.0 官方下载地址:Kafka 3.9.0 - Docker Desktop 4.37 容器图形化工具 官方下载地址:Docker Desktop 4.37 特别说明 - Docker Desktop…...

Linux驱动开发(16):输入子系统–电容触摸驱动实验
有关电容触摸的基础知识内容可以参考野火STM32相关教程,这里只介绍电容触摸驱动的相关内容。 本章配套源码、设备树以及更新固件位于“~/embed_linux_driver_tutorial_imx6_code/linux_driver/touch_scream_GTxxx”目录下。 触摸面板通过双面胶粘在显示屏上&#…...
《深入浅出HTTPS》读书笔记(24):椭圆曲线密码学
《深入浅出HTTPS》读书笔记(24):椭圆曲线密码学 为了保证DH的密钥对不被破解,提升安全性的主要手段就是增加密钥对的长度,但是长度越长,性能越低。 为了解决性能问题,需要…...
现代光学基础5
总结自老师的讲义 yt5 开卷考试复习资料:光探测器与光伏技术 目录 光探测器(Photodetector) 工作原理二极管电路连接方式响应度(Responsivity)微弱光检测超导纳米线单光子探测光电二极管噪声 太阳能电池࿰…...
力扣hot100——贪心
121. 买卖股票的最佳时机 class Solution { public:int maxProfit(vector<int>& a) {if (a.size() 1) return 0;int ans 0;int mi a[0];for (int i 1; i < a.size(); i) {ans max(ans, a[i] - mi);mi min(mi, a[i]);}return ans;} };55. 跳跃游戏 class S…...
vue3如何实现防抖?
第一 防抖就是我们设置一个调用时间,点击后设置时间开始倒计时,如果再次点击会重新倒计时 npm或yarn安装: npm install lodash <template><div click"debouncedInputHandler"><button>打印</button>…...

西安电子科技大学初/复试笔试、面试、机试成绩占比
西安电子科技大学初/复试笔试、面试、机试成绩占比 01通信工程学院 02电子工程学院 03计算机科学与技术学院 04机电工程学院 06经济与管理学院 07数学与统计学院 08人文学院 09外国语学院 12生命科学与技术学院 13空间科学与技术学院 14先进材料与纳米科技学院 15网络与信息安…...
spring mvc源码学习笔记之六
pom.xml 内容如下 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…...
树莓派4b如何连接ov7670摄像头
在树莓派4B上连接和使用OV7670摄像头是一项具有一定技术挑战的任务。这是因为OV7670摄像头是一个原始的CMOS摄像头模块,它通过并行接口与主机通信,而树莓派的GPIO接口通常用于串行接口(如I2C、SPI、UART)通信,不直接支持并行摄像头接口。因此,需要一些额外的硬件和软件工…...

[微服务]分布式搜索Java客户端
快速入门 使用RestClient客户端进行数据搜索可以分为两步 构建并发起请求 代码解读: 第一步,创建SearchRequest对象,指定索引库名第二步,利用request.source()构建DSL,DSL中可以包含查询、分页、排序、高亮等 query…...
如何使用 `uiautomator2` 控制 Android 设备并模拟应用操作_VIVO手机
在 Android 自动化测试中,uiautomator2 是一个非常强大的工具,能够帮助我们通过 Python 控制 Android 设备执行各种操作。今天,我将通过一个简单的示例,介绍如何使用 uiautomator2 控制 Android 设备,执行特定的应用启动、广告跳过以及其他 UI 操作。此示例的目标是自动化…...

在Ubuntu 18.04.6 LTS安装OpenFace流程
一、修改配置:将gcc8,g8作为默认选项 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 100 sudo update-alternatives --config gcc 选择版本,再查看gcc --version sudo update-alternatives --install /usr/bin/g g /usr/bin/g-…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...