背景
由于MySQL供应商内部升级,线上数据库需要主动进行主备切换。切换基于SIP漂移,理论上是毫秒级闪断。但在实际模拟演练的过程中,除了预期内切换瞬间产生的连接异常外,后续半个小时仍不断地产生连接异常,导致问题不可控。
org.springframework.dao.RecoverableDataAccessException:### Error querying database. Cause: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure The last packet sent successfully to the server was 11,603,448 milliseconds ago.; nested exception is java.lang.Throwable at org.springframework.jdbc.support.SQLExceptionSubclassTranslator.doTranslate(SQLExceptionSubclassTranslator.java:100) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ······ Caused by: java.io.IOException: Socket is closed at com.mysql.cj.protocol.AbstractSocketConnection.getMysqlInput(AbstractSocketConnection.java:72) at com.mysql.cj.protocol.a.NativeProtocol.clearInputStream(NativeProtocol.java:803) ... 130 common frames omitted
主备切换后仍有一段时间的报错
分析
排除服务端影响
首先进行一下分析:在报错中显示错误为CommunicationsException: Communications link failure
,并且Caused by: java.io.IOException: Socket is closed
,由此可知此时业务方取出的连接已经关闭了。而The last packet sent successfully to the server was 11,603,448 milliseconds ago
指出上一次成功送出数据包到服务端的时间已经是很久之前了。
为了排除连接在取出后才关闭的可能性,我将连接池的TestOnBorrow开关打开。
testOnBorrow:每次获取连接的时候都会执行validate操作,确保连接取出时有效,对性能有损耗。
在取出前校验,就可以过滤所有取出前就已失效的连接,如果仍有错误,证明MySQL服务端持续有通信问题。
演练结果证明了服务端的清白,报错不再持续出现。
虽然TestOnBorrow能解决无效连接,但作为代价,每一条SQL都会PING一次服务端进行校验,这对性能的影响还是不容小觑的,可不能这样简单交差。
在排除服务端的问题后,我们将目光锁定到应用使用的数据库连接池——Druid。
读源码
既然是获取连接时报错,我们就分析一下获取连接的源代码。
以下为com.alibaba.druid.pool.DruidDataSource#getConnectionDirect
的核心逻辑
for (;;) { //省略从连接池中取出poolableConnection的逻辑 if (testOnBorrow) { boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn); if (!validate) { if (LOG.isDebugEnabled()) { LOG.debug("skip not validate connection."); } Connection realConnection = poolableConnection.conn; discardConnection(realConnection); continue; } } else { Connection realConnection = poolableConnection.conn; if (poolableConnection.conn.isClosed()) { discardConnection(null); // 传入null,避免重复关闭 continue; } if (testWhileIdle) { final DruidConnectionHolder holder = poolableConnection.holder; long currentTimeMillis = System.currentTimeMillis(); long lastActiveTimeMillis = holder.lastActiveTimeMillis; long lastKeepTimeMillis = holder.lastKeepTimeMillis; if (lastKeepTimeMillis > lastActiveTimeMillis) { lastActiveTimeMillis = lastKeepTimeMillis; } long idleMillis = currentTimeMillis - lastActiveTimeMillis; long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis; if (timeBetweenEvictionRunsMillis <= 0) { timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; } if (idleMillis >= timeBetweenEvictionRunsMillis || idleMillis < 0 // unexcepted branch ) { boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn); if (!validate) { if (LOG.isDebugEnabled()) { LOG.debug("skip not validate connection."); } discardConnection(realConnection); continue; } } } } }
当testOnBorrow开启时,会直接调用testConnectionInternal
进行连接检查,连接失效则直接踢出,进入下一次循环。这是我们刚才验证过的可行路径,这说明关闭testOnBorrow时是可以取到失效连接的,说明没有调用testConnectionInternal
。带着这样的假设,我们再看else块。
在else中,首先判断testWhileIdle是否开启,应用配置是开启的(默认也是开启),是则同时取出连接的lastActiveTimeMillis
和lastKeepTimeMillis
,取较大者,与当前时间取差值,如果大于等于timeBetweenEvictionRunsMillis
,则进行testConnectionInternal
校验,同样的,校验不通过则抛弃。
简单讲,在else块中就是durid实现的testWhileIdle机制:在连接取出时,判断如果连接空闲超过指定时间(默认为1分钟),则进行校验,否则直接借出。
在前文中的报错信息里,连接最后一次传输数据是在3个小时前,如此长时间的空闲理应进行校验(校验的正确性我们已在testOnBorrow中验证),然而从现象来看却是没有的,说明计算出的空闲时间没有满足检测门槛。
通过代码分析,lastActiveTimeMillis
就是连接使用时间 ,所以应在3个小时前,至此我们的分析剩下了最后一个可变量:lastKeepTimeMillis
,这个字段从何而来?
连接保活机制
druid中设计了一种连接保活机制,开启后将定时校验连接的有效性。这个任务由DruidDataSource.DestroyTask
承担,在线程池初始化后通过调度线程池以timeBetweenEvictionRunsMillis为周期定时执行(当前为1分钟)。
以下是连接保活中的校验部分
try { this.validateConnection(connec......原文转载:http://www.shaoqun.com/a/893330.html
跨境电商:https://www.ikjzd.com/
cb体系:https://www.ikjzd.com/w/2063
netporter:https://www.ikjzd.com/w/2132
黄劲:https://www.ikjzd.com/w/2426
背景由于MySQL供应商内部升级,线上数据库需要主动进行主备切换。切换基于SIP漂移,理论上是毫秒级闪断。但在实际模拟演练的过程中,除了预期内切换瞬间产生的连接异常外,后续半个小时仍不断地产生连接异常,导致问题不可控。org.springframework.dao.RecoverableDataAccessException:###Errorqueryingdatabase.Cause:com.m
玩转清迈4日游 :http://www.30bags.com/a/414215.html
玩转日本十大最动感刺激的过山车:http://www.30bags.com/a/414592.html
玩转十一 京郊七大户外烧烤地:http://www.30bags.com/a/415338.html
玩转台北:最经典的五条线路推荐:http://www.30bags.com/a/427194.html
口述:我意乱情迷爱上老妈的闺蜜老妈闺蜜儿子:http://lady.shaoqun.com/a/43131.html
一晚上要了小姑娘三次 被男同事伺候的爽了一夜:http://lady.shaoqun.com/m/a/248129.html
在厨房和闺蜜老公出轨 双腿打开对镜子羞辱:http://lady.shaoqun.com/m/a/247383.html
宝贝 你那里好像变大了 我多久没弄你了:http://www.30bags.com/m/a/249945.html
女性可以学习的五种自卫技能,或者她们可以在关键时刻保护自己:http://lady.shaoqun.com/a/429464.html
女朋友不想用安全措施,我慌了:http://lady.shaoqun.com/a/429465.html
在性的道路上,女性应该如何探索自己?:http://lady.shaoqun.com/a/429466.html
没有避孕套的女人活得更舒服:http://lady.shaoqun.com/a/429467.html
没有评论:
发表评论