PHP中Redis/MySQL的长连接

TL;DR

PHP 中针对 Redis / MySQL 的长连接是生命周期级别的长连接,对于同一个进程的每一次请求都不会释放当前连接对象。而针对 TCP Socket 级别的连接是否已断开,则交给操作系统维持。

使用 PDO 对 MySQL 开启持久连接,要注意 PHP 执行的进程数量,不能超过 MySQL 设定的最大连接数。

上述结论的前提是使用 phpredis 扩展,PHP 版本为 5.4.41。

背景

假设某同学使用 PHP 开发了一个队列消费 daemon,某天业务压力较大。实现上每次与 Redis 通信都新建连接,同时本地端口范围过小,导致用尽了本地端口,无法建立连接。

这个时候使用 pconnect 可以有效的减少重复建立连接的成本。使用 ss 等工具可以看到相关的连接数目。

编写如下脚本进行测试:

结果:

可以看到使用了 pconnect 可以有效减少连接数。

Redis 连接的实现

直接翻看 phpredis 扩展源码(2.2.7)。

总体而言,phpredis 通过给定的参数产生一个 RedisSock*,在这一个结构中包含一个 php_stream 结构,利用这一个流式成员,完成与服务端之间的网络通信。

连接时使用 pconnectconnect 的区别在于对于流式对象的管理方式。

pconnect 实现

参数传递

扩展中连接方法的入口是 redis.c 文件中的 redis_connect 函数。解析参数列表部分的逻辑为:

可以看到有一个可选的字符串参数(|之后的spersistent_id,此处先留意这一个变量,后续会使用到。同时,也可以看到 pconnect 方法是可以在调用时指定这个参数的值的。

在后续的 redis_sock_create 函数中,会将当前的 persistent_id 赋值给返回 RedisSock 对象,以供后续创建连接使用。

连接

建立与 Redis 服务器之间的连接工作实际上是由 redis_sock_server_open 函数完成的,即在这一个方法中,通过 php_stream_xport_create 函数将网络连接创建并赋值给 RedisSock 中的 stream 变量,实际上,后续的所有网络读写操作,都会通过这一个变量完成。

这里判断 persistent 的逻辑,就用到了传递进来的 persistent_id 参数,如果没有设定,会根据 Redis 服务器的 IP 和当前设定的超时创建出一个字符串作为值。

这个 ID 用于标记这是一个需要保持的对象(持久性资源)。

PHP 持久性资源

首先,对于Socket/文件等对象,在 PHP 中都是资源对象,而 PHP 扩展中在实现上会将这一个资源对象记录到哈希表 EG(regular_list) 中,通过 zend_list_delete 等函数完成资源引用计数的操作,当引用计数为 0 时,认为资源已无效,删除资源。

redis.c 中的 redis_connect 方法中,当通过 redis_sock_create 创建成功资源对象后,通过 zend_list_insert 完成了当前资源对象的记录:

回到 phpredis 扩展连接创建过程,在创建过程中的 _php_stream_xport_create 函数中,当设定了 persistent_id 之后,会首先在另一个全局哈希表 EG(persistent_list) 中通过 persistent_id 查找到对应的资源对象是否存在,如果存在还会将其尝试注册到 EG(regular_list) 中,减少了无谓的创建过程。

随后会检查连接的可用性,由于对 Redis 服务器是一个 TCP 连接,会通过 xp_socket.c 文件中的 php_sockop_set_optionPHP_STREAM_OPTION_CHECK_LIVENESS 对应分支的逻辑进行连接可用性的检查:

实际上是通过 poll 系统调用判断 socket 对应的 fd 是否可读并且 recv 系统调用返回可读长度小于等于0实现的。

对于 EG(persistent_list) 这一个哈希表,为什么能够实现针对于PHP应用程序持久性的连接呢?答案很简单,这个哈希表只有在 MSHUTDOWN 阶段时才会清理:

EG(regular_list)RSHUTDOWN 阶段就会被清理(这一阶段进程尚未退出):

哪怕是显式的调用了 close 方法,也只是向服务器发送 QUIT 指令并将当前对象的状态设定为已断开状态,实际上并未清理对应的流:

MySQL PDO的连接实现

MySQL PDO的连接实现与 Redis 类似,也是在传递了持久连接的参数后,会将连接对象保存到持久性资源对象的哈希表中。

构建持久化标记ID的方式看起来比 phpredis 扩展稍好一些,用到了 服务器Host/服务器端口/用户名/密码 等元素。

然而如果启用了持久连接,PDO并没有给出主动清理 EG(persistent_list) 的方法,所以,如果要使用这一个特性,需要格外注意不要启动过多的进程,以至于超过 MySQL 设定的最大连接数。

小结

对于开启 Redis/MySQL 的持久连接,PHP 通过将资源对象通过一定的标志存储到 MSHUTDOWN 阶段才会清理的全局哈希表中实现,在针对同一个服务器进行请求时,在 PHP 执行的整个生命周期之内保证了不会重新创建连接。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

This site uses Akismet to reduce spam. Learn how your comment data is processed.