事务未提交和连接未关闭
背景
最近一个项目出现了应用服务器无法登录的现象.
出现现象后,给出了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) 都是系统中的资源.
任何一种资源受到过量的使用都会像癌症一样蔓延到其他的资源.
最终导致系统不可用. 解决问题的核心其实在于找到那种资源收到了过量的使用.
并且找到过量使用的始作俑者. 经历过问题锤炼, 并且有足够的记忆力和敏感度就非常重要了.
遇到问题需要做有根据的假设,并且进行求证.
但是不能无条件无理由的假设, 调查时的错误假设会导致调查失败.
调查失败会导致产品不可用, 影响很坏.