研究下php调用redis的长连接问题。
生命周期
我之前的文章写过php扩展的生命周期,redis扩展也不例外,也是这个流程。
对于长连接可以这样想:长连接解决的问题是重复建立连接所带来的的性能损失,而如果要保持长连接肯定需要一种机制来存储已建立的连接。
而对于php来说,除了一些常驻内存的扩展之外,只有php-fpm能实现这种功能,因为他是常驻内存的。
结合扩展的加载流程可知,redis的长连接一定是在MINIT
阶段做的初始化,在执行php代码的时候将长连接存储在这个空间内,php进程执行完成之后长连接依然存在,从而实现在不同请求之间共享连接。
根据以上推理,翻一下源码,在redis.c的833行,有这样一个调用:
/* Register resource destructors */
le_redis_pconnect = zend_register_list_destructors_ex(NULL, redis_connections_pool_dtor,
"phpredis persistent connections pool", module_number);
根据代码的注释和调用,大概能猜到这里是注册了一个phpredis的持久化连接池。这里的redis_connections_pool_dtor
,应该是一个资源的析构函数,在开发者没有显示调用close
的关闭资源的时候由引擎调用。
上面的如果懂了,可以推测出php-fpm的进程退出时会断开所有的长连接。
保活
一个tcp长连接建立后,需要某种机制来保活,也就是发送keepalive包。
而phpredis文档上并没有说明keepalive的相关配置,猜测是并没有实现主动保活的功能(操作系统应该有机制可以实现,默认时间可以通过sudo sysctl -a | grep keepalive
查看)。
查看redis的配置文件,发现有tcp-keepalive
,配置文件注释也表明这个选项是用来保活的。
新版本默认配置为300秒,也就是每五分钟发送一个tcp包给客户端连接,保持连接始终为ESTABLISHED
。
通过抓包可以看出:
sudo tcpdump -i any tcp port 6379
通过抓包,也能确认在php-fpm进程退出的时候会有关闭连接的数据包产生。
注意事项
在phpredis扩展的github首页,有这样一句话:
So be prepared for too many open FD’s errors (specially on redis server side) when using persistent connections on many servers connecting to one redis server.
大意是使用长连接要注意打开太多文件描述符的错误,造成这个问题的原因是redis保存了大量的客户端连接,最终超过了限制,目前默认限制是10000(maxclients
)。
不过phpredis是根据tcp+ip来识别不同的连接,测试过程中发现一次http请求,php-fpm假设有5个worker进程,可能会一次创建小于5个的长连接。
这个问题产生的错误是max number of clients reached
,涉及到这个问题的配置项为timeout
,默认0,也就是说不超时。所以如果开启了长连接,同时有keepalive
,那么这些连接将一直存在。
还有一个事情就是php-fpm的配置项pm.max_requests
,这个选项用来避免fpm的worker进程内存泄露。也即比如worker进程处理了100个进程之后,由master重新生成worker进程来使其重生,放在长连接的场景下,会出现长连接的age
有所不同的情况。
在redis-cli内可以通过下面两个命令查看客户端列表:
client list
info clients
最后
本文主要讲了phpredis长连接的生命周期和如何保活,以及结合配置文件说明一些可能出现的问题。
(完)
- 本文作者:吴泽辉
- 本文链接:https://mutex.top/posts/4bce9829/
- 发表日期:2020年6月13日
- 版权声明:本文章为原创,采用《知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议》进行许可