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

记一次压测排查死锁

概述

应用背景

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

环境

  • 机器: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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352