导读:云计算时代的服务端网络环境越来越复杂。不但要考虑实际的物理网络,也要考虑到SDN/安全等技术的影响。理论上说,网络对应用开发无感知,然而有时候也并非如此。本文作者记录了一种阿里云上Redis/MySQL的灵异现象,并且记录了问题原因,给出了解决方案。
引子:Redis client library 连接 Redis server 超时
差不多一两年前,在阿里云上遇到一个奇怪的 Redis 连接问题,每隔十来分钟,服务里的 Redis client 库就报告连接 Redis server 超时,当时花了很大功夫,发现是阿里云会断开长时间闲置的 TCP 连接,不给两头发 FIN or RST 包,而当时我们的 Redis server 没有打开 tcp_keepalive 选项,于是 Redis server 侧那个连接还存在于 Linux conntrack table 里,而 Redis client 侧由于连接池重用连接进行 get、set 发现连接坏掉就关闭了,所以 client 侧的对应 local port 回收了,当接下来 Redis 重用这个 local port 向 Redis server 发起连接时,由于 Redis server 侧的 conntrack table 里 <client_ip, client_port, redis-server, 6379> 四元组对应状态是 ESTABLISHED,所以自然客户端发来的 TCP SYN packet 被丢弃,Redis client 看到的现象就是连接超时。
解决这个问题很简单,打开 Redis server 的 tcp_keepalive 选项就行。 然而当时没想到,这个问题深层次的原因影响很重大,后果很严重!
孽债:"SELECT 1" 触发的 jdbc4.CommunicationsException
最近生产环境的 Java 服务几乎每分钟都报告类似下面这种错误:
由于有之前调查 Redis 连接被阿里云异常中断的先例,所以怀疑是类似问题,花了大量时间比对客户端和服务端的 conntrack table,然而并没有引子中描述的问题,然后又去比对多个 MySQL 服务器的 sysctl 设置,研究 iptables TRACE,研究 tcpdump 抓到的报文,试验 tw_reuse, tw_recyle 等参数,调整 Aliyun 负载均衡器后面挂载的 MySQL 服务器个数,都没效果, 反而意外发现一个**新问题**,在用如下命令不经过阿里云 SLB 直接连接数据库时,有的数据库可以在 600s 时返回,有的则客户端一直挂着,半个多小时了都退不出来,按 ctrl-c 中断都不行。
当时检查了一个正常的数据库和一个不正常的数据库,发现两者的 wait_timeout 和 interactive_timeout 都是 600s,思索良苦,没明白怎么回事,然后偶然发现另外一个数据库的 wait_timeout=60s,却一下子明白了原始的 "select 1" 问题怎么回事。
我们的服务使用了 Hikari JDBC 连接池[1],它的 idleTimeout 默认是 600s, maxLifetime 默认是 1800s,前者表示 idle JDBC connection 数量超过 minimumIdle 数目并且闲置时间超过 idleTimeout 则关闭此 idle connection,后者表示连接池里的 connection 其生存时间不能超过 maxLifetime,到点了会被关掉。
在发现 "select 1" 问题后,我们以为是这俩参数比数据库的 wait_timeout=600s 大的缘故,所以把这两个参数缩小了,idleTi