php使用redis连接数过高问题
2019-03-19
最近发生了几次php连接数过高导致的问题,由于业务体量上涨导致php机器扩容,某个库的连接数过高导致redis cpu跑满。
系统解释一下php为什么会出现连接过高的问题。
php进程模型
根据上图能够得到:
每个redis实例的连接数 = php的实例数 * 每个实例上的进程数
比如有200个redis实例,每个php实例上有100个进程,那么每个redis上会存在 200 * 100 = 2w个连接。
当请求量上涨时,php业务需要扩容,扩容之后会加大redis的连接数
(200 + 100)个php实例 * 100 进程数 = 3w连接
经过一次故障,对于redis库我们大概得出的结论是:
单个redis实例所能承担的连接数上限为5w,3w以下认为无风险,3w-4w之间存在风险,4w-5w之间出现超时,对业务产生影响,超过5w redis server的cpu会上升直至100%,redis开始拒绝服务。
(其他服务有待测试,某单实例连接数6w业务层没有异常,虽然redis号称10w连接无问题,实际因为命令不一样,所用cpu不一样)
所以现有的php服务面临了一个困境,业务扩容会导致redis的连接数上涨,redis的连接数上涨又会拖慢业务,进入了一个死锁。
解释了每次php负载上升都同时会有redis连接数的各种报警
疑问
Q:扩容redis有没有效果,我扩10000个redis难道也解决不了吗
A:很遗憾,是没有效果的,此举反而会加重php实例的连接数,如下图
redis的连接数只和php的实例数以及每个实例上的进程数相关,所以单实例的连接数不会变小,同时由于redis的实例数增多,会使php建立的连接数增多。
解决方案
- php层建立连接池,现在的连接是进程级别复用的,而非实例级别复用
- 但是php没有非常成熟的本地连接池,有一些例如swoole等,但是测试起来需要成本
- php连接集群时单个进程只和一个实例建立连接,例如php的32333进程只和39400端口建立连接
- 这种情况只适用于队列的方式,其他情况下不适合
- 业务拆分,将s1改为s1+s2从库,业务上根据不同业务连接不同从库,在解决连接数过高的时候临时采用了这个方案,是有效果的,如下图
问题:但非长久之计,业务体量上涨迟早有一天s1和s2的连接数也会到达瓶颈
- php和redis之间建立proxy层,类似codis,twemproxy
- 会有性能损耗,但是从长期来看是一个根本解决问题的方法