MQ异常补偿方案
MQ异常补偿方案
一次数据库连接获取失败的经历....
Ebay 活动技术方案
本文档使用 MrDoc 发布
-
+
首页
一次数据库连接获取失败的经历....
## 问题描述 在2022-09-16。刊登小组里一个亚马逊项目出现了获取连接失败异常   ## 第一次解决方案 当时的第一反应就是寻找DBA,看是否是服务与Tidb之间网络有问题。经过排查后发现网络是没问题的,Tidb也是正常运行的。当时看到问题里面的描述说需要配置==wait_timeout==这个属性后。就连忙追加了这个属性,并且让DBA把Tidb的超时时间也拉长。 ## 第二次解决方案 万万没有想到的是,在第一次解决方案处理了以后不到半天光景。又爆出了这个问题。而且还不止是Tidb有这类问题,连带着==Mysql==数据源也暴出此类问题。然后我们就尝试着分析业务场景,可能造成的慢SQL,以及百度和google。网上统一的都说是链接检测机制有问题。这个时候我们翻出了我们的配置 ```yml #初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 initialSize: 5 #最大连接池数量 maxActive: 50 #最小连接池数量 minIdle: 5 #获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 maxWait: 60000 #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭 poolPreparedStatements: false #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 maxOpenPreparedStatements: -1 #用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 validationQuery: SELECT 1 FROM DUAL #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 testOnBorrow: false #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 testOnReturn: false #建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 testWhileIdle: true # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 有两个含义: 1) Destroy线程会检测连接的间隔时间2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 180000 # 秒 validationQueryTimeout: 10000 maxPoolPreparedStatementPerConnectionSize: 20 filters: stat,wall #通过connectProperties属性来打开mergeSql功能;慢SQL记录 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 #合并多个DruidDataSource的监控数据 useGlobalDataSourceStat: true ``` 然后尝试把==minEvictableIdleTimeMillis==属性改成了300000 => 180000毫秒。因为当时第一反应是不是超出了Mysql的会话超时时间。结果后面经过查阅资料发现Mysql默认的会话超时时间是8小时。然后又查阅了Tidb的会话超时时间是由自身以及代理中间件HAProxy或LVS一起决定的。因此这个修改操作其实是无意义的。 Mysql:  Tidb:  然后我们回到了配置本身,挨个检查配置。突然发现了==validationQueryTimeout==这个配置项由之前的研发人员配置成了10000秒(==其实最开始我们都以为这个配置项的单位是毫秒,直到检查时才发现了是秒==)。这个配置其实是一个检测连接是否可用一个测试SQL的超时配置。当时我们就在想如果是一个正常连接肯定是没有影响。但是如果是一个坏连接就意味着这个地方会导致一直让连接池==无法释放==这个坏连接。当时我们都以为见识到了真相,因此改完了以后就重新部署上线 ## 第三次解决方案 结果在第二次发布完后的2小时,又继续暴出该问题。这个时候就在想到底是啥原因?然后在和DBA的一句对话过程中  这个时候我开始审视起我们项目中的jar包问题。因为同时让==Tidb和Mysql一起==暴出这个问题,必然跟外部没有太大关系,一定是我们项目自身的配置或代码问题。在这个时候我突然想起来在报获取连接异常之前一直在打ERROR日志警告。这个时候我重新去翻了一下日志,发现了Druid 版本是1.1.6, 然后尝试着去durid github issue上尝试着找问题。没想到还真有一篇文章。但是这篇文章写得跟我的场景不是太一致。随即PASS掉。然后顺着这个思路,我继续缩小范围。把Druid1.1.6 communication link failure关键字一起在github上搜索。结果真的找到一个issue 《druid分布式事物mysql8小时 超时无法使用》 https://github.com/alibaba/druid/issues/2299 然后尝试着看了以后才终于找到一直忽略的点。我们用的是==AtomikosDataSourceBean== !!!分布式事务数据源。而不是DriudDataSource..... ```java @Bean(name = DataSourceMabangPublishamzp.DATASOURCENAME) @Autowired public DataSource dataSourceMabangPublishamzp(Environment env) { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); Properties prop = build(env, "spring.datasource.druid.mabangpublishamzp."); ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); ds.setUniqueResourceName("mabangpublishamzp"); ds.setPoolSize(20); ds.setXaProperties(prop); return ds; } ``` 我感觉仿佛终于找到了真相的切入点。然后通过这个切入点直接去翻源码, 才发现这个数据源并不是用的平时数据源标准的validQuery配置,而是用它内置自身的==testQuery==来做为检测连接是否正常的机制 以下是==AtomikosXAPooledConnection.java== 与 ==AtomikosNonXAPooledConnection.java==连接检测源码 ```java protected void testUnderlyingConnection() throws CreateConnectionException { if ( isErroneous() ) throw new CreateConnectionException ( this + ": connection is erroneous" ); String testQuery = getTestQuery(); if (testQuery != null) { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": testing connection with query [" + testQuery + "]" ); Statement stmt = null; try { stmt = connection.createStatement(); //use execute instead of executeQuery - cf case 58830 stmt.execute(testQuery); stmt.close(); } catch ( Exception e) { //catch any Exception - cf case 22198 throw new CreateConnectionException ( "Error executing testQuery" , e ); } if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": connection tested OK" ); } else { if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": no test query, skipping test" ); } } ``` 至此所有真相都解开了。然后把配置改成了 ```java @Bean(name = DataSourceMabangPublishamzp.DATASOURCENAME) @Autowired public DataSource dataSourceMabangPublishamzp(Environment env) { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); Properties prop = build(env, "spring.datasource.druid.mabangpublishamzp."); ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); ds.setUniqueResourceName("mabangpublishamzp"); ds.setPoolSize(20); ds.setTestQuery("SELECT 1 FROM DUAL"); ds.setXaProperties(prop); return ds; } ``` 重新上线以后。经过观察接近1天左右业务运行正常,没有问题 ## 注意的点 虽然业务运行正常,但是还是会==偶尔==一段时间频繁抛出获取链接失败异常。不过因为有了testQuery所以会去等待获取到好的连接再去执行业务,所以业务没啥问题。 其实切tidb之前肯定也有这个问题。连接失败是受两方面因素影响。 第一方面是连接的确在连接池里面超时了然后去拿就会出现这个异常。 第二方面是Mysql或Tidb单方面把这个连接断掉,但是在程序内你是无法感知的。所以拿到这个"正常"的链接你去访问就会报链接获取错误的问题。 然后为什么切tidb之前没有这个问题呢? 至于这个点现在没有去深究。在我看来有可能去换jdbc或者换druid版本, 找到一个适配的。又或者更改mysql超时时间可以改长,又或者改让druid心跳时长小于mysql超时时间等这些方面出发应该是有会有一个好的真相。 ## 尾声 经过此次的排查,让我对数据源内部的细节也多了一层认知。也让我对一些未知的事务拥有了敬畏之心。希望大家以后使用工具时前能更好的把握利弊和细节
thread
2022年9月21日 14:19
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码