spring并发与事务,springboot处理高并发的总结
00-1010 1.背景2。主要参数说明:2.1 Tomcat主要并发参数说明2.2数据库连接池参数2.3数据库连接数3、测试程序4、jmeter测试4.1、快组4.2、慢组4.3、对比分析5、问题与优化5.1、问题5.2、故障排除5.3、核心5.4、调优6、优化实验6。
00-1010在目前快速开发的环境下,大部分时候我们的应用都测试的很好,并发的时候形式环境一塌糊涂。不知道并发的相关参数,看不懂测压报告,是很多程的基本状态。本文重点分享长事务和长连接导致的并发检查和优化的思路和例子。
长事务会导致长连接,长连接未必是因为长事务,因果关系先搞清楚。
主要技术:
spring boot:2 . 5 . 12 my batis-spring-boot-starter:2 . 1 . 2 druid-spring-boot-starter:1 . 2 . 9 my SQL-connector-Java:8.0 Tomcat:9 . 0 . 54
目录
1、背景
server : port : 8080 compression : enabled : true Tomcat : accept-count : 511 max-connections : 8192 threads : max 3360 0 200 basedir 3360/u01/app/base/logs/Tomcat连接超时3360 60000保持活动状态超时3360 60000 * *应该分别考虑计算密集型和IO密集型。
* * * max-connections:*:* *表示可以在服务器和客户端之间建立多少个连接,即保持的连接数。默认情况下,Tomcat有8192个连接。cpu可能没时间帮你处理,但是可以保持连接。这个参数是客户感知的参数。
与accept-count:服务器内核相关,它是传输到服务器内核的客户端请求的积压值。该值和服务器内核参数net.core.somaxconn的最小值是最终有效的TCP内核满队列值。它表示在最大连接数达到预设值后,服务器内核可以建立的连接数。这个连接存储在内核中,没有被tomcat拿走。tomcat中缺省值为100,Centos7.x版本中内核net.core.somaxconn为128。如果超过max-connections和accept-count之和,新的连接将被拒绝,即服务将被直接拒绝(拒绝的连接将被直接返回)。
检查CentOS:sysctl-a grep net.core.somaxconn的net . core . somaxconn参数。
2、主要参数释义:
data source : type : com . Alibaba . druid . pool . druid data source druid : driver-class-name : com . MySQL . CJ . JDBC . driver #属性连接池initial-size : 5 max-active 3360 140min-idle 3360 10 #获取配置的连接等待超时时间max-wait : 30000 connect-properties . slowsqlmillis 333333首先,这个值不要越大越好。至少,它应该小于数据库本身配置的最大连接数。如果超过最大数量,将在数据库级别抛出类似“太多连接”的错误。mysql数据库连接的默认最大数量是151。一般在配置数据库连接池应用组件时,不要超过这个数量,需要预留一些连接数给维护人员。
00-1010数据库连接数在上面2.2中已经提到,它决定了数据库支持的最大并发数。
支票
看mysql的最大连接数:
show variables like %max_connections%;
查看mysql目前的连接数:
show global status like Max_used_connections;
如果你的应用配置连接数超过数据库预设的最大数,并且客户端不断并发的发起数据库连接,连接池数量就会不断创建与数据库的物理连接,如果该连接是各种原因导致的数据库长连接(例如:长事务),那么一旦超过数据库的最大值,应用就会报连接数太多的错误。
3、测试程序
两个对外暴露的url:
5000毫秒的长连接操作100毫秒的短连接操作Controller:
@GetMapping("/slowGetAll") public Result<Object> slowGetAll(){ return Result.ok(testUserService.queryAll()); } @GetMapping("/fastGetAll") public Result<Object> fastGetAll(){ return Result.ok(testUserService.fastGetAll()); }
service: 向数据库里插入两个用户,并查询最后一个用户的信息,在两次insert操作中间加入一个耗时操作。
@Transactional(rollbackFor = Exception.class) @Override public List<TestUser> fastGetAll() { TestUser user1 = getTestUser("李四", "jerry"); this.testUserDao.insert(user1); slowMethod(customProperties.getFastMillis()); TestUser user2 = getTestUser("张三", "tom"); this.testUserDao.insert(user2); return this.testUserDao.queryByBlurry(user2); } @Transactional(rollbackFor = Exception.class) @Override public List<TestUser> queryAll() { TestUser user1 = getTestUser("王五", "jack"); this.testUserDao.insert(user1); slowMethod(customProperties.getSlowMillis()); TestUser user2 = getTestUser("赵柳", "amy"); this.testUserDao.insert(user2); return this.testUserDao.queryByBlurry(user2); } private void slowMethod(int milliseconds){ try { int i = globalCount.incrementAndGet(); System.out.println("slowMethod start -->"+i); Thread.sleep(milliseconds); System.out.println("slowMethod end -->"+i); } catch (InterruptedException e) { e.printStackTrace(); } }
注意,这里的长连接使用使用Thread.sleep实现,不是真正的数据库长事务。
4、jmeter测试
开启400线程,测试10轮,分两组:
快速处理的短连接4000次。慢速处理的长连接4000次。
4.1、快速组
druid连接池主要结果分析:
指标值解释事务时间分布0,0,3735,265,0,0,0事务运行时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s]连接持有时间分布0,0,0,3583,417,0,0,0连接持有时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s]在JMeter中的吞吐量为:305.4/second
4.2、慢速组
druid连接池主要结果分析:
指标值解释事务时间分布0,0,3599,401,0,0,0事务运行时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s]连接持有时间分布0,0,0,0,4000,0,0,0连接持有时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s]在JMeter中的吞吐量为:26.3/second
4.3、对照分析
事务时间分布:非数据库操作的耗时对事务时间分布是没有影响的,快速组和慢速组分布区间基本相同。
连接持有时间分布:快速组大约90%的时间分布在100ms-1s
,慢速组100%分布在1-10 s
,符合预期的设置。
吞吐量:快速组比慢速组快了接近10倍。
小小结论:在慢速组中,虽然事务的分别时间较短,但是发起该事务的连接一直没有被释放,导致并发能力断崖式下降。
5、问题与优化
5.1、问题
在本次测试中,影响应用并发性能主要体现在长连接持有时间,当服务器处理某个请求耗时较长会导致并发能力直线下降,这个耗时可能会因为数据库长事务、长计算、或发起对外慢速同步的API请求等等原因导致。
5.2 、排查
通过druid monitor监控可以查看很多与数据库连接的参数和实际发生状态,本例中,主要需要找到事务时间分布和连接持有时间即可初步定位问题,然后通过SQL监控找到发生的SQL语句逐步排查定位到JAVA代码块,找到代码块一般都能分析出实际的问题。
5.3、核心
在整个应用生态中,最宝贵的资源就是数据库连接,数据库相关业务密集的系统中,首先需要保证尽可能少的持有数据库连接。所以才催生出数据库连接池这些技术。第二宝贵的是磁盘IO,所有对磁盘IO的操作尽可能少,所以催生出数据库索引、缓存等技术,对于需要直接操作磁盘IO的计算来说,能用顺序读或写,就不要用随机读或写。
5.4、调优
优化并发需从几个方面出发:
服务器本身的参数,比如CPU核数,高性能磁盘位置等,通过服务器参数对应设置前文所述的几个应用参数,比如服务器CPU核心数为16个,一般通用业务应用下,缺省的200个线程已经足够。一旦确认服务器配置等基本的参数,并拍脑袋设置了一些应用参数后,就需要启动压测测试各参数配比下最好的服务器和应用性能。确认你的业务系统是计算密集型还是IO密集型,针对性的编写测试用例。最后确认业务系统乃至服务器的性能瓶颈链,输出整体性能报告。在实际生产中,一旦检测到实际并发与性能报告相差太大就可以启动排查程序。
6、优化实验
6.1 手动事务
代码优化背景和目标:
首先长连接的事实不可改变,因为有可能这个计算或调用就是需要耗费5秒的时间,如果能减少,则属于另外的优化逻辑;在这个背景下,我们改善的目标是减少数据库连接的持有时间;从实例代码里,真正需要事务的操作是两个insert,slowMethod和query查询都不需要,所以启动手动事务就可以减少数据库连接的持有时间。使用Springboot的手动事务模板。
6.2、优化第一组测试
代码优化如下:
@Resource private TransactionTemplate transactionTemplate;@Override public List<TestUser> optimizedGetAll() { slowMethod(customProperties.getSlowMillis()); TestUser user1 = getTestUser("王五", "jack"); TestUser user2 = getTestUser("赵柳", "amy"); transactionTemplate.execute(status -> { this.testUserDao.insert(user1); this.testUserDao.insert(user2); return Boolean.TRUE; }); return this.testUserDao.queryByBlurry(user2); }
druid连接池主要结果分析:
指标值解释事务时间分布0,0,2295,1626,79,0,0事务运行时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s]连接持有时间分布2307,1041,2735,1838,79,0,0,0连接持有时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s]在JMeter中的吞吐量为:37.3/second
测试分析:
首先连接持有时间大幅度下降,原先慢速组100%的样本数据在1-10 s
区间,现在改区间只有0.98%的数据。我们发现连接持有时间分布样本数据总和是8000,而不是原本慢速组的4000,并且在0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s
都有分布,说明一个问题,query查询单独占用了数据库连接。我们知道数据库连接是宝贵资源,query单独占用连接是否有问题?从上面的测试结果分析,query虽然单独占用资源,但是时间很短,并且也是复用了连接池的资源,相当于把长事务分摊到短操作中。是否还有更优配置呢?所以我们还需要做一组测试,把query放入事务中试试看。本次连接持有时间分布总样本中位数参考值是 1458262 ms。最后,吞吐量有所提升,总体上这个优化方案是有效果的。
6.3、优化第二组测试
代码优化如下:
@Override public List<TestUser> optimizedGetAll2() { slowMethod(customProperties.getSlowMillis()); TestUser user1 = getTestUser("王五", "jack"); TestUser user2 = getTestUser("赵柳", "amy"); return transactionTemplate.execute(new TransactionCallback<List<TestUser>>() { @Override public List<TestUser> doInTransaction(TransactionStatus status) { testUserDao.insert(user1); testUserDao.insert(user2); return testUserDao.queryByBlurry(user2); } }); }
druid连接池主要结果分析:
指标值解释事务时间分布0,0,3778,222,0,0,0事务运行时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100-1 s, 1-10 s, 10-100 s, >100 s]连接持有时间分布0,0,2278,1611,111,0,0,0连接持有时间分布,分布区间为[0-1 ms, 1-10 ms, 10-100 ms, 100ms-1s, 1-10 s, 10-100 s, 100-1000 s, >1000 s]在JMeter中的吞吐量为:38.0/second
测试分析:
对比一二组,首先事务分布时间是改善的,但是并不明显;本次连接持有时间分布总样本中位数参考值是 1474400 ms,对比一二组,第二组比第一组多耗费 16138 ms;在该样本的情况下,第一组实际持有连接的时间更短,压力更为平均。将不必要的query放入事务本身就是不推荐,本示例中query查询的数据量较小,如果数据量较大,从理论上分析,会明显影响事务的提交。吞吐量基本无变化,这个是符合预期的。所以这次优化,推荐使用第一组的方式。
7、总结
在系统初次上线前,可以考虑编写测试用例用压测的方式拿到服务器的性能指标。在系统完成开发后,可以考虑对简单、中等、高复杂度的API分别进行压测摸清API的性能体现,以及对比服务器性能指标,有可能在上线前就能排查出问题。在系统上线后,如果遇到应用性能下降或并发问题,可以通过观察连接池和SQL分析定位大致的java代码块,解决问题最终还是需要针对性编写测试用例再次复盘测试。大部分情况下,系统缺省的参数都够你使用,任何一次参数优化调整都应该有理论支撑和实践证明,云调参张口就来谁都会。并发调优一定要抓住关键目标,找到核心资源,分析并发瓶颈链路。数据库连接池还有很多参数是可以帮助调优分析的,本文没有介绍,jmeter的压测的方式也是多种多样,应根据不同的场景调整。并发调优是一个复杂的过程,从应用所有的关联点都可能出现问题,本文只是从开发角度,针对请求长连接以及数据库长事务做了简单的分析和推测。到此这篇关于Springboot并发调优之大事务和长连接的文章就介绍到这了,更多相关Springboot并发调优之大事务和长连接内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。