长连接(persistent_connection)和php-fpm的关系

项目原本连接memcache用的是php的memcache扩展,现在为了与php7兼容,打算更换到memcached扩展。在更换的时候,因为原来有长连接的需求,在memcache扩展里只要用pconnect方法就可以了,但是memcached的长连接是这样实现的

1
2
3
4
public Memcached::__construct ([ string $persistent_id ] )

persistent_id
By default the Memcached instances are destroyed at the end of the request. To create an instance that persists between requests, use persistent_id to specify a unique ID for the instance. All instances created with the same persistent_id will share the same connection.

如上所述,它是通过一个unique id来标识这个连接,同样的id会共享连接。看到这里我产生了一个问题,类似与mysql,redis,memcache这样的长连接,连接信息是如何保存的。像cgi那样的方式还好说,就保存在当前的进程下,进程结束了连接也就没有了。但是如果对fpm而言,fpm进程原本就常驻内存,而且server上一般会开很多空闲的fpm进程出来,那么创建了一个长连接之后,不同的fpm进程间会进行通信吗,在a进程打开长连接的用户如果他的下一个请求被b进程handle,可以共享进程a的连接吗?fpm的worker进程被正常重启后,长连接信息是否还会保存?为此我做了如下实验。
先只启动两个fpm进程,pid分别为10751和10752:
Markdown
然后写如下代码:

1
2
3
4
5
<?php
$memcache = new Memcached('test');
$memcache->addServer('10.60.1.237', 11516);
echo "pid: ".getmypid()."</br>";
var_dump($memcache->getServerList());

先在网页上执行一次代码,显示
Markdown
说明是pid为10751的worker进程执行了这段代码,且打开了一个长连接。下面将代码改为

1
2
3
4
5
<?php
$memcache = new Memcached('test');
//$memcache->addServer('10.60.1.237', 11516);
echo "pid: ".getmypid()."</br>";
var_dump($memcache->getServerList());

然后刷新页面,显示
Markdown
说明,各个fpm间并不能共享长连接信息,如果不同的请求分发至不同的fpm进程,将会重新打开全新的连接
下面,执行kill命令将两个worker进程都kill掉,让master进程重启两个全新的fpm进程,然后再刷新页面,发现,依然没有连接信息了,说明长连接的生命周期与fpm进程一致,fpm若重启,长连接就会断开
最后,在使用memcached的长连接的时候,还有一点需要特别注意的:长连接会在addServer的时候被建立,若别的请求对相同的id去长连接一些已经建立过连接的host&port,memcached扩展不会自动去重,也会新打开新的连接,所以要在代码上规避这一点,否则会建立越来越多不必要的连接,最终core dump,这一点官方也有详细的说明:

When using persistent connections, it is important to not re-add servers.
This is what you do not want to do:

1
2
3
4
5
6
7
8
<?php
$mc = new Memcached('mc');
$mc->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$mc->addServers(array(
array('mc1.example.com',11211),
array('mc2.example.com',11211),
));
?>

Every time the page is loaded those servers will be appended to the list resulting in many simultaneous open connections to the same server. The addServer/addServers functions to not check for existing references to the specified servers.
A better approach is something like:

1
2
3
4
5
6
7
8
9
10
<?php
$mc = new Memcached('mc');
$mc->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
if (!count($mc->getServerList())) {
$mc->addServers(array(
array('mc1.example.com',11211),
array('mc2.example.com',11211),
));
}
?>