1. 超时情况出现
这事还得从几个星期前说起,某天同事说项目的数据库连接有些问题。项目跑着跑着就会出现Communications link failure,The last packet successfully received from the server was * millisecond ago.The last packet successfully sent to the server was * millisecond ago这种错。
这个之前项目也有类似的情况,我上一篇博文也有说明。MySQL服务器默认的空闲时间超过8个小时(如果你的应用接的是VIP或者MYCAT,则以VIP或者MYCAT的空闲超时时间为准),mysql将自动断开该连接,而连接池却认为该连接还是有效的,当应用申请使用该连接时,就会报以上错。
我们这里的架构是:jdbc–>HA–>Mycat–>MySql
所以如果HA对连接的空闲时间设置为10分钟自动断开连接的话,而且jdbc的空闲连接为10分钟以上,就会出现这个报错。
比如:jdbc(> 10min)–>HA(=10min)
解决方案:
要么将HA的改大,要么将jdbc的改小(我们项目用的是BoneCP连接池,于是修改idleMaxAge为5min),初步观察又正常了。
2. 问题重现
这事过了一两天后,同事忽然又找我说,项目跑着跑着,会卡死(卡死的代码位置为向数据库插入数据)。然后30min后,程序抛出第一点所说的异常,服务又能继续正常运行了。
当时第一反应,是不是idleMaxAge不生效,30min又是怎么来的?当时考虑说是Mycat设置的空闲超时时间就是30min,会不会就是因为这个导致连接中断。(上面说我们的项目是连的HAProxy(10min),后面我们因为调试需要,直连了mycat(30min))
于是DBA将mycat的空闲连接超时时间改为了1h后,重新运行项目,发现依然会卡死30min。可见并非服务端的时间所影响,也许更多原因在连接池上。
3. 更换连接池
因为上面分析可能是连接池,于是考虑更新连接池。
原来我们用的boneCP,于是改为druid。
boneCP核心参数配置
<property name="idleMaxAge" value="5"/>
druid核心参数配置
<property name="timeBetweenEvictionRunsMillis" value="600000"/>
<property name="minEvictableIdleTimeMillis" value="360000"/>
<property name="testWhileIdle" value="true"/>
timeBetweenEvictionRunsMillis:
(1) Destory线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis,则关闭物理连接。
(2)testWhileIdle的判断依据。
minEvictableIdleTimeMillis:把空闲时间超过minEvictableIdleTimeMillis毫秒的连接断开, 直到连接池中的连接数到minIdle为止 连接池中连接可空闲的时间,毫秒。
testWhileIdle:
申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
其实可以看到,设置上都没有什么问题的。实在无法解析30min分钟的卡死现象。(如果有经验的朋友可以给点建议,谢谢)
4、无意的尝试
这事又过几天,同事跟我说,去掉事务后,程序正常了。于是重新梳理了业务逻辑。如下:
1、服务A就是卡死30分钟。
2、去掉的是服务B,服务C的事务。可见他们操作的是同一张表,也有可能操作该表的同一条记录。
3、服务B\C的update的语句是update xxx where id=? and phoneNum=?
于是考虑,会不会因为服务B、服务C的事务导到锁表;以致于服务A无法插入数据。但是innoDB使用的行锁,怎么会引起锁表。
百度得知:
字段没有索引,即使使用wehre条件也会进行表级锁
如果有索引,会锁定对应where条件中索引值的所有行,可理解为对该索引值进行了索引
观察自己的sql语句,id是一个主键(primary key)[即是有主键],在表里不存在多条记录。即使有多条记录,也只是部分行而已,怎么会导致锁表?最多是行锁(此时锁表的结论还只是一个假设)
5. 请教专家
实在没法,只好请教公司里的DBA主管。
她说:
服务B/C里的 update事务太多,高并发下会产生堆积,会产生和表锁一样的效果。从而导到insert不进去。
先不去验证这个说法的正确性,可同事说服务B/C的update事务很少,只给service的包添加了事务,不存在高并发的情况,而且只开了5个线程,同一时间只会有5个update语句在执行(一台服务器)。
6. 重新梳理项目配置
于是确认一下同事说的是不是真实的情况,看了一下spring-mybatis的配置如下:
<tx:advice id="mybatis-txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*find*" read-only="true" />
<tx:method name="*get*" read-only="true" />
<tx:method name="*load*" read-only="true" />
<tx:method name="*check*" read-only="true" />
<tx:method name="*save*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*input*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*insert*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*add*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*update*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*delete*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*batch*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<!-- Service类的public方法 -->
<!-- <aop:pointcut id="serviceHandle" expression="(execution(* com.cccc.einvoice.*.service..*.*(..)))
or (execution(* com.cccc.einvoice.api.service..*.*(..)))" /> -->
<aop:pointcut id="serviceHandle"
expression="(execution(* com.cccc.einvoice.*.*(..)))" />
<aop:advisor advice-ref="mybatis-txAdvice" pointcut-ref="serviceHandle" />
</aop:config>
看到aop的扫描路径,同事说的事务只对service生效的代码被屏蔽,换之的是更大范围的包扫描。而刚好mybatis的Mapper也包含在里面,那相当所有的操作都会包括事务,形同高并发。
—–结—–
1、注意事务的aop扫描路径。
—- 未解决问题—-
1、卡死的30min到底是哪里影响?