在分布式系统中,用连接池缓存住连接,来节省连接反复销毁和创建的成本,是一种很常见的做法。但是在高可用的分布式系统中,”切换”是一个非常普遍的操作,切换就会造成连接池失效的问题。
TCP 虽说是”有连接的”,但这个连接实际上是一个虚拟连接。客户端用一段内存保存连接的五元组(源端口,源IP,目的端口,目的IP,协议),服务端也保存一个这样的五元组,双方就认为有这么一个连接了,可以通过这个连接收发。
但是假如客户端保存有这个五元组,服务器因为某种原因将这个五元组删掉了,就会造成客户端单方面认为有这么一个连接,而服务器不承认(反过来也有这个问题,但是服务器丢失连接的情况更加普遍也更加严重)。当客户端想使用这个连接向服务器发送请求的时候,会被服务器拒绝。
保持一个连接池的做法会使这个问题更加严重,因为连接用完之后不会马上销毁,等待下一次使用,这段时间天知道会发生什么。比如说应用连接数据库这种情况,一般会有连接池保持和数据库的连接。假如 DB 重启了、DB 端因为超时或者其他什么原因把连接关闭了(即把内存的五元组删掉)、DB 出现故障,从一个IP迁移到另一个IP了,都会造成客户端连接池中的连接不可用,这个时候客户端通过这些”坏连接”向DB发送请求,就会失败。(多久时间会返回失败也是一个问题,这受 socketTimeout 参数和 Linux 系统参数影响,具体可见这篇 Datebase timeouts.)
除了DB之外,像 Nginx 保持向后端应用的连接等负载均衡设备通常也有这种问题,只不过有的组件自身选好了方案处理掉了,有的是暴露出来选项让你自己设置。本文讨论处理这种问题的几种思路。
一、失败了就是败了,重建就是
这是成本最低的方式,连接池的实现一般都会包括如果连接不可用,就删除并重建。影响是会失败一些请求(数量=连接池的数量)。
当然有些时候我们是可以预测需要切换DB的,有条件的话可以将业务切换走,然后再切换DB。业务回切之前先对应用发送一波压测流量,用假的业务流量刷掉不用的连接。
二、主动保持
之前在博客中介绍过 TCP Keepalive,可以正好解决这种场景。就是设置一个定时器,定时检查连接是否还可用。
对于 TCP 来说,由于这是一个基于流的协议,所以可以发送一段长度为0的片段来检验是否能收到 ACK。对于 MySQL 来说,要看驱动程序的实现,比如如果实现了 ping 函数可以使用 ping;如果没有 ping 可以使用 SELECT 1。对于 redis 来说也有 ping 命令。Jboss 提供的 background-validation 参数,就是这个原理。
一般来说这个方案是复杂度低,并且比较有效的。缺点是当分布式的连接池巨大的时候,比如上千台机器的话,每台 DB 要承受测活的连接数是 机器数 * 连接池大小,资源消耗是很大的。
三、使用较小的 idle timeout 和 小连接池
连接池一般都提供设置两个参数:
- idle timeout:当 idle 时间超过之后,会主动回收;
- min connections:连接池最小要保持多少连接;
通过设置 idle timeout,可以保证没有连接是开头说的那种很长时间没用过的连接,因为都会回收掉了。
但是这里会存在一个问题:min connections 要保证连接池有多少个连接,假如设置的是10,平时只用5的话,那么因为 idle timeout 参数会回收5个连接,导致连接池会新建5个新连接。过一段时间,因为有一些用不到又会被回收,就造成了在反复创建连接。
假如采取另一种策略:回收 idle timeout 的连接但是保证连接数要大于 min connections,假如连接数不大于 min connections,那么即使 idle timeout 也不回收。这样可以解决上面的问题,但是会造成无法保证 connection pool 里面的连接都是新鲜的,回到我们最初的问题了。
所以这种方案其实又要做一个 trade off,如果能保证连接需求的比较稳定,需要的连接数总是大于 min connections 又不至于大太多,倒是可以用在这种方案。
四、每次使用之前检查一次
顾名思义,每次使用连接之前先检查一下这个连接是否可用。成本太高一般不用,对客户端来说会增加延迟,对DB来说会增加压力。
实现的话,可以看下 DBCP 的 TestOnBorrow。
五、HAProxy
HAProxy 并不能说是一个思路,只是一个现成的工具,它是一个正向代理,实现了连接的测活和切换。HAProxy 的原理我还没研究,但是应该不出这几种思路。甚至是可以让你选的,毕竟它配置项目那么多。
HAProxy 怎么用呢?考虑有多个应用连接多个数据库的场景,从原来的直接连接变成通过 HAProxy 连接。当需要切换的时候,应用不需要感知 DB IP 的变化,而是 HAProxy 更改后端 DB 的 IP,从而对应用透明了,应用只需要一个 DB 集群的 URL 就可以了。
另一方面,也节省了 DB 的连接数。