java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Springboot 大事务和长连接

Springboot并发调优之大事务和长连接

作者:我是属车的

这篇文章主要介绍了Springboot并发调优之大事务和长连接,重点分享长事务以及长连接导致的并发排查和优化思路和示例,具有一定的参考价值,感兴趣的可以了解一下

1、背景

在当前这个快速开发的环境下,很多时候我们的应用都是测试好好的,正式环境并发一高就一团糟。不了解并发相关参数,看不懂压测报告,是很多程序猿的基本状态。本文重点分享长事务以及长连接导致的并发排查和优化思路和示例。

长事务会导致长连接,长连接未必是因为长事务,因果关系先搞清楚。

主要相关技术:

2、主要参数释义:

2.1 tomcat主要并发参数释义

server:
  port: 8080
  compression:
    enabled: true
  tomcat:
    accept-count: 511
    max-connections: 8192
    threads:
      max: 200
    basedir: /u01/app/base/logs/tomcat
    connection-timeout: 60000
    keep-alive-timeout: 60000      

**threads.max:**表示服务器最大有多少个线程处理请求,默认200,实际上这个参数超过服务器核心数太多反而会降低服务器cpu处理速度,对于计算密集型和IO密集型应分开考虑设置该参数。

**max-connections:**表示服务器与客户端可以建立多少个连接数,即持有的连接数。tomcat缺省是8192个连接数,cpu未必有时间给你处理,但是可以保持连接。这个参数是客户感知型参数。

accept-count: 与服务器内核相关,是客户端传入给服务器内核,请求的backlog值,该值与服务器内核参数net.core.somaxconn取小后的值为最终起效的TCP内核全队列值。它表示在max-connections值达到预设的值后,服务器内核还能建立的连接数,这个连接保存在内核,还未被上层应用(tomcat)取走。该值在tomcat中默认是100,在Centos7.x版本中内核net.core.somaxconn是128。如果超过max-connections和accept-count总和,新的连接会被拒绝,即直接拒绝服务(直接返回connection refused)。

查看CentOS的net.core.somaxconn参数:sysctl -a|grep net.core.somaxconn

2.2 数据库连接池参数

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:     
      driver-class-name: com.mysql.cj.jdbc.Driver
      #连接池属性
      initial-size: 5
      max-active: 140
      min-idle: 10
      # 配置获取连接等待超时的时间
      max-wait: 30000     
      connect-properties.slowSqlMillis: 2000

max-active: 数据库连接池数据连接最大数量,即连接池物理打开数据库的最大数量。这个参数一般开发人员都会错误的设置,首先这个值不是越大越好,最起码它得小于数据库本身配置的最大连接数,如果超过后再向数据库发起连接,就会在数据库层面抛出类似"too many connection"的错误。mysql数据库默认最大连接数为151。一般配置数据库连接池应用组件的时候,不要超过这个数,并且需要留一部分连接数给维护人员使用。

2.3 数据库连接数

上文2.2已经提到数据库连接数,它决定了数据库支持的最大并发数。

查看mysql的最大连接数:

show variables like '%max_connections%';

查看mysql目前的连接数:

show global status like 'Max_used_connections';

如果你的应用配置连接数超过数据库预设的最大数,并且客户端不断并发的发起数据库连接,连接池数量就会不断创建与数据库的物理连接,如果该连接是各种原因导致的数据库长连接(例如:长事务),那么一旦超过数据库的最大值,应用就会报连接数太多的错误。

3、测试程序

两个对外暴露的url:

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轮,分两组:

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、调优

优化并发需从几个方面出发:

6、优化实验

6.1 手动事务

代码优化背景和目标:

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

测试分析:

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

测试分析:

7、总结

到此这篇关于Springboot并发调优之大事务和长连接的文章就介绍到这了,更多相关Springboot并发调优之大事务和长连接内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文