- 娱乐
mysql连接被断开后的系统处理
时间:2010-12-5 17:23:32 作者:百科 来源:综合 查看: 评论:0
内容摘要: 凌晨两点,我被一个报警电话拽起来。生产环境的订单服务全部超时,日志里密密麻麻的"connection reset"。数据库连接断了,连接池里的那些连接全成了废纸,程序还在傻乎乎地往上面写数据。这就是
凌晨两点,接被我被一个报警电话拽起来。断开生产环境的系统订单服务全部超时,日志里密密麻麻的处理"connection reset"。数据库连接断了,接被连接池里的断开那些连接全成了废纸,程序还在傻乎乎地往上面写数据。系统这就是处理典型的MySQL连接被服务端断开后,客户端毫不知情,接被继续使用死连接导致的断开雪崩。 连接断开的系统根源往往不在应用层。MySQL的处理`wait_timeout`默认八小时,但云厂商的接被代理层、防火墙的断开idle timeout可能只有几分钟。你的系统连接池里躺着看似健康的连接,实际上早就被中间设备静默掐断了。第一次查询时,TCP层才发现对端已经消失,抛出一个让人摸不着头脑的异常。 处理这种问题,第一道防线是连接池的自动回收机制。HikariCP的`maxLifetime`应该比数据库的`wait_timeout`短几秒,让池子主动淘汰老连接。但光靠这个不够——如果连接是在两次心跳之间被断开的,你依然会踩雷。真正稳妥的做法是,每次从池子里拿连接时,都做一次轻量级验证。`SELECT 1`的开销几乎可以忽略,但能确保你拿到的连接是活的。别嫌麻烦,这比在业务代码里处理一个莫名其妙的`CommunicationsException`要划算得多。 就算做了预检,也防不住查询执行到一半时连接被断开。这时候你需要的是重试机制,但必须小心。不是所有SQL都适合无脑重试,如果执行的是UPDATE或者INSERT,重试可能导致重复写入。解决方案是给业务操作设计幂等键,或者在事务启动前先获取一个全局锁。更简单的做法是,把重试范围限定在只读查询上,写操作直接抛出异常让上层决定怎么补偿。 我见过一个团队,他们把所有数据库操作都包了一层重试逻辑,结果某个深夜数据库主从切换,所有写请求都重试了三次,把刚恢复的主库活活压垮。重试要有退避策略,指数退避加随机抖动是基本操作。而且得设上限,三次不行就放弃,别跟数据库死磕。 还有一个容易被忽略的点——连接断开后的清理工作。数据库连接断了,但相关的Statement、ResultSet可能还占着资源。Java里这些对象是强引用关联在Connection上的,连接一旦失效,它们也得跟着释放。有些程序员只关了Connection,没关Statement,在连接池场景下这会导致内存泄漏,因为池子会复用那个坏掉的连接对象。 真正高明的做法,是让系统对连接断开这件事无感。用MyCat或ShardingSphere这类中间件做读写分离和故障转移,应用层只管发SQL,连接是否可用由中间件来保证。但引入中间件又带来新的复杂度,你得权衡值不值得。小规模系统,自己写个代理类包装一下连接,出问题时重新获取,也就几十行代码的事。 那天晚上的故障最后怎么解决的?我看了下监控,发现连接池的`minimumIdle`设成了0,流量高峰时池子里的连接全被借光,新建连接又赶不上数据库的accept速率。调整参数后,再给所有查询加上超时时间,问题就消失了。MySQL连接断开不可怕,可怕的是你的系统对断连没有感知,还在用旧连接做无用功。每次查询前验证一下,每次异常时重试有度,每次清理时释放干净——这三点做到,大部分连接问题都伤不到你。