美文网首页
spring事务隔离导致数据库连接耗尽而死锁

spring事务隔离导致数据库连接耗尽而死锁

作者: SAikeilee | 来源:发表于2020-06-19 16:25 被阅读0次

    记一次压测排查死锁

    概述

    应用背景

    应用是公司内部的基础设施平台,会接收到多个内部平台的数据上报,考虑到后期可能接入平台增多,故对应用展开压力测试查看应用的在高并发情况下表现。压测接口主要是上报数据的接口,观察接口的稳定性。

    环境

    • 机器:2-core、4G DGRAM + 30G Disk

    • JDK1.8,-xms 2G -xmx2G

    • Tomcat

      Tomcat接受请求过程:在accept队列中接收连接(当客户端向服务器发送请求时,如果客户端与OS完成三次握手建立了连接,则OS将该连接放入accept队列);在连接中获取请求的数据,生成request;调用servlet容器处理请求;返回response。

      accept-count为socket请求连接队列数量,maxThreads为线程池最大数量,maxConnections为最大同时连接数

      • 接受和处理的最大连接数maxConnections:500
      • 请求处理最大线程数maxThreads:28
      • 队列长度accpet-count:200(默认值为100)
    • druid

      • 初始大小initial-size: 2
      • 最大数据库连接数max-active:20
      • 最小空闲数量min-idle:2
      • 最大等待毫秒数max-wait:600

    测试指标

    • JVM内存运行稳定,无OOM,没有不合理大对象
    • CPU、内存、网络、磁盘、文件句柄占用平稳
    • 无频繁线程锁、线程数平稳
    • 业务线程负载均衡
    • 异常率小于0.1%

    现象

    TPS:Transaction Per Second 事务每秒

    QPS: Query Per Second 请求每秒

    当用户一次操作(一个连接)只请求一个接口,TPS和QPS没有任何区别

    当TPS达到30左右,无论如何增添线程数(用户数),TPS不会再上升

    排查

    1. 首先进行top,系统负载Load avg平稳,CPU使用率平稳(不高),一般计算密集型应用 CPU 使用率偏高 load 偏低,IO 密集型相反。内存占用在70%左右,相对平稳。

    2. 使用jstat -gcutil查看没有频繁fullGC youngGC。

    至此,我开始怀疑是不是代码的质量写的有问题。

    1. 接着按惯例我还是再用jstack查看堆栈,结果发现好多个线程在Waiting状态,从下往上查看,主要是在申请数据库连接时候getConnection()。

    在最开始,有多个线程在获取数据源时候卡住,导致数据库连接池连接被占满,而后所有线程全部处于等待状态,引发死锁。

    最后定位到id生成器的一段代码上面去

    image-20200619145716098

    然后向上定位,发现原来事务级别为3,REQUERIES-NEW,最后发现代码位于自定义的ID生成器上面

    image-20200619145955568

    先概括一下死锁原因,Spring事务传播引发连接池死锁。

    当服务需要分布式id时,会首先从数据库中获取一个start_id,然后将start_id更新成start_id+step。那么从start_id~start_id+step段内对的所有id,都属于当前这个服务了。如果start_id用完了,就会按照相同的流程重新申请一个start_id。

    线程本身开启事务(每个事务占用一个数据库连接),然后使用id生成器申请Id,Id生成器发现Id不够用,于是再开启一个事务向数据库拿id,发现连接不够用了,于是等待连接池别的线程释放连接,而别的线程也在等待id生成器的id,形成互相等待局面——死锁。

    Spring事务级别3,其实是TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说无论如何都创建一个事务

    解决

    1. 改变事务隔离级别,PROPAGATION_REQUIRES_NEWPROPAGATION_REQUIRED

      • PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    2. 增加连接池 getConnection 最大等待时间的配置。

      如果没有获取到连接一定时间则会抛出异常,结束这个线程。至于如何配置,不同的连接池的配置项不同,具体可参考对应的连接池官方文档配置。如果防止部分连接执行时间太长或者数据源泄露,还可以加上Connection最大存活时间配置。

    3. 不使用同一个数据库连接池

      正常来说,id生成的数据库实例应该单独配置实例

    4. 增加事务超时时间配置。(一般情况下不推荐,因为如果sql执行时间超过了超时时间,事务也会等待对应的sql执行完后结束,而在下一次执行sql时候报错)

      通过spring事务注解时候,加上超时时间的属性配置。

      @Transactional(timeout = 60)     //代表事务60秒超时
      

    本文纯粹是作者对工作中同小组遇到的压测排查记录,感谢同事wangxi

    相关文章

      网友评论

          本文标题:spring事务隔离导致数据库连接耗尽而死锁

          本文链接:https://www.haomeiwen.com/subject/ynxbxktx.html