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

目录
  • 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.1 手动事务
    • 6.2、优化第一组测试
    • 6.3、优化第二组测试
  • 7、总结

1、背景

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

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

主要相关技术:

  • SpringBoot: 2.5.12
  • mybatis-spring-boot-starter: 2.1.2
  • druid-spring-boot-starter: 1.2.9
  • mysql-connector-java: 8.0+
  • tomcat: 9.0.54

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:

  • 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并发调优之大事务和长连接内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot集成WebSocket长连接实际应用详解

    前言: 一.WebSocket之初出茅驴 官方定义:WebSocket是一种在单个TCP连接上进行全双工通信的协议.WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输.是真正的双向平等对话,属于服务器推送技术的一种. 太官方啦,还是博主过来翻译一下吧 :WebSocket技术只需要service和client建立一次连接,就能实现服

  • springBoot service层事务控制的操作

    springBoot使用事物比较简单,在Application启动类s上添加@EnableTransactionManagement注解,然后在service层的方法上添加@Transactional注解 @Transactional属性 属性 类型 描述 value String 可选的限定描述符,指定使用的事务管理器 propagation enum: Propagation 可选的事务传播行为设置 isolation enum: Isolation 可选的事务隔离级别设置 readOnly

  • Springboot通过aop实现事务控制过程解析

    spring的事务控制本质上是通过aop实现的. 在springboot中使用时,可以通过注解@Transactional进行类或者方法级别的事务控制,也可以自己通过spring提供的事务管理器手动控制事务 一. @Transactional注解进行进行类或者方法级别的事务控制 不需要进行特别的设置,按照正常的配置整合spring和mybatis后,在需要进行事务控制的类上或者方法上加上 @Transactional注解,即可对其进行事务控制. 二.手动控制事务 当需要在一个方法的内部进行事务控

  • 详解Springboot事务管理

    在Spring Boot事务管理中,实现自接口PlatformTransactionManager. public interface PlatformTransactionManager { org.springframework.transaction.TransactionStatus getTransaction(org.springframework.transaction.TransactionDefinition transactionDefinition) throws org.

  • SpringBoot 注解事务声明式事务的方式

    springboot 对新人来说可能上手比springmvc要快,但是对于各位从springmvc转战到springboot的话,有些地方还需要适应下,尤其是xml配置.我个人是比较喜欢注解➕xml是因为看着方便,查找方便,清晰明了.但是xml完全可以使用注解代替,今天就扒一扒springboot中事务使用注解的玩法. springboot的事务也主要分为两大类,一是xml声明式事务,二是注解事务,注解事务也可以实现类似声明式事务的方法,关于注解声明式事务,目前网上搜索不到合适的资料,所以在这里

  • springboot整合rocketmq实现分布式事务

    1 执行流程 (1) 发送方向 MQ 服务端发送消息. (2) MQ Server 将消息持久化成功之后,向发送方 ACK 确认消息已经发送成功,此时消息为半消息. (3) 发送方开始执行本地事务逻辑. (4) 发送方根据本地事务执行结果向 MQ Server 提交二次确认(Commit 或是 Rollback),MQ Server 收到Commit 状态则将半消息标记为可投递,订阅方最终将收到该消息:MQ Server 收到 Rollback 状态则删除半消息,订阅方将不会接受该消息. (5)

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

    目录 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.1 手动事务 6.2.优化第一组测试 6.3.优化第二组测试 7.总结 1.背景 在当前这个快速开发的环境下,很多时候我们的应用都是测试好好的,正式环境并发一高就一团糟.不了解并发相关参数,看不懂压测

  • SpringBoot项目调优及垃圾回收器的比较详解

    一.SpringBoot项目在外部Tomcat启动时加载两次 如下所示,spring标志出现两次(截取部分代码) . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|=====

  • G1垃圾回收器在并发场景调优详解

    目录 序言 G1概览 1.最大堆大小 2.Region大小 3.获取默认值 三种GC模式 1.新生代回收 2.混合回收 3.Full GC 默认参数 1.堆内存 2.新生代内存回收 3.混合回收 垃圾在堆中流转 1.对象如何进入老年代 (1)大对象直接到老年代 (2)动态年龄判断 2.高并发加速进入老年代 调优步骤 1.设置垃圾回收器 2.设置堆大小 3.元空间设置 4.GC停顿时间 5.新生代大小 调优实践 1.频繁的YGC 2.频繁的Mixed GC (1)大对象 (2)元空间 3.Full

  • springboot内置tomcat调优并发线程数解析

    目录 前言 参数 线程池核心线程数 线程池最大线程数 请求最大连接数 accept-count tomcat线程池处理机制 总结 前言 本文解析springboot内置tomcat调优并发线程数的一些参数,并结合源码进行分析 参数 线程池核心线程数 server.tomcat.min-spare-threads:该参数为tomcat处理业务的核心线程数大小,默认值为10 线程池最大线程数 server.tomcat.max-threads:该参数为tomcat处理业务的最大线程数大小,默认值为2

  • Jvm调优和SpringBoot项目优化的详细教程

    一.Jvm调优. 参考文章 1.先看一下未设置JVM参数的情况,默认情况下,没有设置任何Jvm参数. idea中安装VisualVM监控jvm的图文教程 2.设置Jvm参数. 配置参数: 关于这些设置的JVM参数是什么意思,参考Jvm调优. -XX:MetaspaceSize=128m (元空间默认大小) -XX:MaxMetaspaceSize=128m (元空间最大大小) -Xms1024m (堆最大大小) -Xmx1024m (堆默认大小) -Xmn256m (新生代大小) -Xss256

  • SpringBoot内置tomcat调优测试优化

    问题 怎么配置springBoot 内置tomcat,才能使得自己的服务效率更高呢? 基础配置 Spring Boot 能支持的最大并发量主要看其对Tomcat的设置,可以在配置文件中对其进行更改.我们可以看到默认设置中,Tomcat的最大线程数是200,最大连接数是10000. 这个不同SpringBoot 版本可能有所细微差别.本文测试基于Springboot 2.0.7.RELEASE 默认配置 /** * Maximum amount of worker threads. */ priv

  • SpringBoot JVM参数调优方式

    目录 SpringBoot JVM参数调优 各种参数 SpringBoot jar包启动设置JVM参数 配置初始化堆和最大堆的大小 SpringBoot JVM参数调优 各种参数 参数名称 含义 默认值 说明 -Xms 初始堆大小 物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. -Xmx 最大堆大小 物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大

  • sqlserver性能调优经验总结

    相信不少的朋友,无论是做开发.架构的,还是DBA等,都经常听说"调优"这个词.说起"调优",可能会让很多技术人员心头激情澎湃,也可能会让很多人感觉苦恼.当然,也有很多人对此不屑一顾,因为并不是每个人接触到的项目都很大,也不是每个人做的项目都对性能要求很高. 在主流的企业级开发和互联网应用中,数据库的重要性是不言而喻的,而数据库的性能对于整个系统的性能而言也是至关重要的,这里无庸赘述. sqlserver的性能调优,其实是个很宽广的话题.坦白讲,想从概念到实践的完全讲

  • 关于MySQL性能调优你必须了解的15个重要变量(小结)

    前言: MYSQL 应该是最流行了 WEB 后端数据库.虽然 NOSQL 最近越来越多的被提到,但是相信大部分架构师还是会选择 MYSQL 来做数据存储.本文作者总结梳理MySQL性能调优的15个重要变量,又不足需要补充的还望大佬指出. 1.DEFAULT_STORAGE_ENGINE 如果你已经在用MySQL 5.6或者5.7,并且你的数据表都是InnoDB,那么表示你已经设置好了.如果没有,确保把你的表转换为InnoDB并且设置default_storage_engine为InnoDB. 为

  • java虚拟机之JVM调优详解

    JVM常用命令行参数 1. 查看参数列表 虚拟机参数分为基本和扩展两类,在命令行中输入 JAVA_HOME\bin\java就可得到基本参数列表. 在命令行输入 JAVA_HOME\bin\java –X就可得到扩展参数列表. 2. 基本参数说明: -client,-server: 两种Java虚拟机启动方式,client模式启动比较快,但是性能和内存管理相对较差,server模式启动比较慢,但是运行性能比较高,windos上采用的是client模式,Linux采用server模式 -class

随机推荐