高并发写利器-组提交,我的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-…...

C 语言的整型提升问题
目录 引言 一、什么是整型提升 二、为什么会有整型提升 三、整型提升的规则 四、整型提升的影响 五、如何避免整型提升带来的问题 六、总结 引言 在 C 语言中,整型提升(Integer Promotion)是一个常常被忽视但却非常重要的概念。理解整…...

第0章 机器人及自动驾驶SLAM定位方法全解析及入门进阶学习建议
嗨,各位同学大家好!笔者自985硕士毕业后,在机器人算法领域已经深耕 7 年多啦。这段时间里,我积累了不少宝贵经验。本专栏《机器人工程师带你从零入门SLAM》将结合下面的SLAM知识体系思维导图及多年的工作实战总结,将逐…...

video.js视频播放上手
html案例 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>videojs视频播放</title> </head> <link href"https://cdnjs.cloudflare.com/ajax/libs/video.js/7.3.0/video-js.min.cs…...

【LLM-Agent】Building effective agents和典型workflows
note Anthropic的工程经验: 大道至简,尽量维护系统的简洁;尽量让过程更加透明(因为你依赖的是LLM的决策,如果只看输出不看过程,很容易陷入难以debug的情况);对LLM需要调用的工具&am…...

《量子比特大阅兵:不同类型量子比特在人工智能领域的优劣势剖析》
在科技的前沿,量子比特与人工智能的融合正开启一扇全新的大门。不同类型的量子比特,如超导、离子阱、光量子等,在与人工智能结合时展现出独特的优势与劣势。 超导量子比特 超导量子比特是目前应用较为广泛且研究相对成熟的量子比特类型。它…...

《探秘开源大模型:AI 世界的“超级引擎”》
《探秘开源大模型:AI 世界的“超级引擎”》 一、开源大模型崛起之路二、开源大模型发展历程回顾(一)早期奠基:理论突破与初步实践(二)快速发展:百花齐放的模型格局(三)当下态势:走向成熟与多元融合三、开源大模型核心技术剖析(一)Transformer 架构:基石之稳(二)…...

el-table行列转换简单版,仅限单行数据
原始数据格式如下,如果不是此格式,请转换成以下格式在进行以下操作 [{ label: name, value: Tom },{ label: age, value: 25 },{ label: country, value: UK } ]代码如下 <template><el-table :data"tableData" style"width: …...

2025年1月4日蜻蜓q旗舰版st完整开源·包含前后端所有源文件·开源可商用可二开·优雅草科技·优雅草kir|优雅草星星|优雅草银满|优雅草undefined
2025年1月4日蜻蜓q旗舰版st完整开源包含前后端所有源文件开源可商用可二开优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined 产品介绍: 本产品主要贡献者优雅草科技优雅草kir|优雅草星星|优雅草银满|优雅草undefined-青史留名,时光如川浪淘…...

SQL把字符串按逗号分割成记录
在 SQL 中,可以通过以下方法将字符串按逗号分割,并将每个分割的值作为单独的记录插入到结果集中。以下是针对不同数据库系统的实现方法: 1. 使用 STRING_SPLIT(SQL Server 2016) STRING_SPLIT 是 SQL Server 提供的内置…...

C#设计模式(行为型模式):观察者模式
C#设计模式:观察者模式,让对象间通信更优雅 在软件开发中,我们经常会遇到一个对象的状态发生改变,其他对象需要自动更新或做出相应反应的场景。例如: GUI事件处理: 当用户点击按钮时,按钮需要…...