事务未提交和连接未关闭

济南小老虎 / 2024-01-25 / 原文

事务未提交和连接未关闭


背景

最近一个项目出现了应用服务器无法登录的现象.
出现现象后,给出了jstack和应用的log等信息. 

jstack 里面所有的 http的nio 线程都在parked状态. 
然后log里面出现了大量的获取不到链接的提示. 

所以第一反应是因为事务未提交导致的连接池泄露. 
但是后续与同事一起看问题时发现, 并没有未提交的事务. 
所以感觉自己可能对 事务未提交和连接未关闭的场景产生了混淆.

所以想着能够总结一下两种场景, 希望能够学习和提高. 

现象

所有的工作线程都进入 parked的状态:
"http-nio-5200-exec-179" #59186 daemon prio=5 os_prio=0 tid=0x00007f4d941ca800 
nid=0x28e3da waiting on condition [0x00007f4b976f9000]
   java.lang.Thread.State: TIMED_WAITING (parking)

服务器没有压力 CPU/内存都很低

应用服务器的日志显示: 
Failed to obtain JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: 
tenancy-connection01 - Connection is not available, request timed out after 30000ms

数据库显示有大量的正在执行的SQL,并且链接未关闭,能够查询出来.

区别和理解-事务未提交

事务未提交和连接未关闭其实是两个维度的问题. 

事务未提交可能链接已经被关闭了. 
这种问题很有可能是 finally 里面有connection的 dispose或者是close的命令
但是存在有 未被捕获的异常导致 transaction 没有commit/rollback 的情况. 

导致应用线程(大部分为http-nio)返回给连接池管理的数据库链接(hikari后者是durid)里面还存在未提交的事务. 
因为应用已经将connection返回给了 pool , 所以pool认为此connection 可以进行服用. 
然后再给其他申请connection的 thread 时, thread 要commit 事务时就会报错, 因为前面存在未rollback的事务.
导致自己的产品出现异常.  所以这个时候, 就会出现施暴者没有反馈, 但是受害者再惨叫的情况. 

此时需要关注的是 链接关闭时, 如果打开链接的线程堆栈忘记了事务的 commit/rollback 需要记录下来. 
这类错误肯定会导致应用上有人报错, 但是报错的不是施暴者, 提前发现很重要. 可以避免问题爆发. 

区别和理解-链接未关闭

链接未关闭的情况时 事务可能未提交, 也可能已经提交了. 
不管事务有没有提交, 那么就需要足够多的异常操作,将连接池占满,导致应用不可用. 

此时需要关注hikari等的连接数指标. 
如果连接数指标单调递增没有下降的趋势, 并且没有应用反馈存在事务条失败的问题时: 
很有可能出现了链接未关闭的问题

需要注意一般情况下连接未关闭时一个比较缓慢的过程. 并且如果业务操作非常低频率
可能很久也发现不了问题. 

但是当存在一种比较2B的代码时可能瞬间将应用服务器的连接池打满
那就是 for循环或者是递归调用 sql执行, 同事没有关闭数据库链接. 
比如连接池设置为100 , 然后一个大于100 的循环内打开链接进行insert 或者是update
并且链接不关闭.  那么很快应用机会不可用,出现宕机的情况. 

防范方法

1. 可以通过代码的规范化检查来避免此类问题. 
2. 通过代码规范, 避免手工建立连接,并且不进行释放来处理. 
3. 测试时需要考虑足够多的场景, 尤其是数据量, 避免小数据量的情况无法发现问题.
4. 必须增加监控指标, 避免测试环境经常重启导致问题外泄. 
5. 举一反三,增加研发培训, 对新员工增加严重问题代码的培训和宣贯.

其他理解

java运行其实是通过jvm虚拟机来进行处理的. 
jvm其实可以理解为一个小型的linux系统. 
既然是虚拟机, 是操作系统, 那么资源就是系统最重要的资产. 
关闭资源就是系统最重要的工作. 

CPU/内存/线程/文件句柄/链接(http,db) 都是系统中的资源.
任何一种资源受到过量的使用都会像癌症一样蔓延到其他的资源.
最终导致系统不可用. 

解决问题的核心其实在于找到那种资源收到了过量的使用. 
并且找到过量使用的始作俑者. 

经历过问题锤炼, 并且有足够的记忆力和敏感度就非常重要了.
遇到问题需要做有根据的假设,并且进行求证. 
但是不能无条件无理由的假设, 调查时的错误假设会导致调查失败.  
调查失败会导致产品不可用, 影响很坏.