环境说明
本次测试机器为开发人员的工作电脑(数据库与测试程序均运行在一台机器上)。
机器参数:
-
处理器:Intel(R) Core™ i5-6300HQ CPU @ 2.30GHz 2.30 GHz (四核)
-
内存:12G
测试环境:
-
数据库:MySQL-8.0.22
-
java:JDK 1.8.0_221-b11
sequence数据库序列方式的测试及优化
测试变量说明:
**bufferSize:**序列缓存数量,即每次与数据库交互会缓存多少个主键
在本次测试中,bufferSize分别为:10、1000、100000、100000000
**seqNum:**序列数量,即维护了多少个递增序列
在本次测试中,seqNum分别为:1、10、100
单位说明:
本次测试结果中,平均时间的单位均为微秒(us),吞吐量的时间单位为秒(s),吞吐量单位为 ops/s
原sequence方式测试结果如图所示:
平均时间:
吞吐量:
分析及优化
-
理论上,如果bufferSize足够大,数据库交互次数很低,主键基本是在内存缓存中取得,平均时间会很低。测试结果表明,当bufferSize很大时,平均时间为2000us左右,即2毫秒,吞吐量为1750ops/s,性能随bufferSize增大的影响并不明显。显然此时瓶颈并不在数据库交互,而是在其他地方存在导致瓶颈的资源竞争。
-
在koca配置中心(该模块使用了sequence主键生成方式)性能测试过程中发现,使用批量主键获取的方法,会导致数据库连接占满。经过分析和阅读资料发现,在使用Spring注解事务管理时,进入具体方法前,spring会根据注解中的属性,判断是否需要一个新连接,如果需要,则从连接池获取一个连接。连接并不是在执行 sql 语句时获取的,而是在方法执行前获取。如果判断需要获取新连接,即使方法中只有一句System.out.println(“hi”),执行该方法时也会有一个连接被占有。在原有方式中,为了避免业务中事务对主键序列更新的影响,使用Spring注解事务管理时,设置了属性propagation = Propagation.REQUIRES_NEW,即当前方法中的事务不受其他事务影响。此时执行此方法时,spring会单独获取连接管理此事务,因此在获取主键时,即使当前不需要数据库交互更新序列,也会不断有连接被占有。
对此,参考美团的数据库号段模式实现进行了优化,比较大的改动如下:
-
调整事务和锁,不将事务直接注解在获取主键的方法实现上,而是在需要数据库交互时,再获取连接;调整原本的锁,将锁类改为锁序列对象。
-
参考美团的实现,引入双buffer模式,即每个序列存在两个缓存,其中一个对外提供主键时,另一个获取空闲连接并更新,一个缓存的主键使用完后,立即切换更新好的缓存。
-
参考美团的实现,引入弹性bufferSize方式,即根据实时请求量,动态的调整bufferSize大小,用以应对并发量突变而预设的bufferSize不合理导致瓶颈的情况。
以上优化中,控制好锁和事务已经可以得到很好的性能,双buffer避免了更新序列导致的主键获取超时,弹性bufferSize使其功能更强。
优化后性能结果:
平均时间:
吞吐量:
优化后平均时间在1us左右,吞吐量可达百万级。