闲来无事研究一下 PHP 的 MySQL 持久连接问题。在 MySQL 扩展的年代,应该用的是 mysql_pconnect
,可是那时候我还没有开始接触 PHP, 所以我们直接上 PDO。
首先说一下本次测试用的环境。
关键还要看一下 PHP 的配置。
注意其中的最重要的参数 pm.max_children=1
, 这决定了只能有一个 FPM 的 worker 进程来处理所有请求。这样把问题简化更容易发现特征。
我们知道, PHP 的 FPM 有一个 master 进程和若干个 worker 进程, 而 worker 进程并不是像最早的 fastcgi 一样每次处理完一个请求之后就销毁, 下次再来请求需要重新启动。也就是说一个 worker 进程是可以处理多个请求的。这给“持久连接”提供了理论基础。 那么到底它能不能支持持久连接呢?如果支持持久连接, 它的特征又是什么呢?下面直接上代码。
我在之前的文章里也提到过 MySQL 的 general_log,按这里的描述配置一下就可以很清晰的看到哪个连接在请求服务器了。
反复执行 curl http://fpm.org/index.php
(我给 fpm.org 配置了 hosts), 就可以看到不断变化的 lastInsertId 了。 但这不是重点, 要看两个现象。
用 root 账户登录 MySQL, 执行 show processlist;
可以看到类似如下的输出:
可以看到, 有两个客户端和服务器保持了连接。是谁呢?第一个是 Ubuntu(其实是 Debian)维护的 MySQL 包自带的默认管理员用户, 其实表示的就是当前的这个 MySQL cli 连接。另外一个当然就是刚才调用 curl
通过 PHP 的 pdo 创建的了。
现在再来看 general_log,
可以看到多次请求服务器端都是同一个线程 ID(14). 参考这里
那么怎么验证它是由当前这个 FPM 的 worker 维持的呢?很简单, 重启一下 FPM 再看它有没有变化就知道了。
sudo systemctl restart php-fpm.service
可以看到一个新的 worker 已经在工作了。 现在重新访问刚才的地址
先再去执行一下 show processlist;
可以看到 14 已经不在了。因为维持 14 这个连接的 fpm worker 已经挂了,当然对应的服务器的线程也已经销毁了。但 15 出现了。那么这几次请求用的是不是 15 呢?
当然是。
所以结论很明显了,在 FPM 模式下是可以使用 MySQL 持久化连接的。
所以理论上也可以实现 MySQL 连接池。有时间可以研究一下。 想了想是没有办法实现连接池的, 因为一个 worker 只能维持一个长连接,无法和别的 worker 共享, 只能通过配置 pm.max_children
来让 FPM 维持的长连接没有那么多不要超过 MySQL 的最大连接数.
不过这是一个危险操作, 因为你也看到了, 我在写这篇文章的过程中在没有手动重启 FPM 进程之前这个长连接是一直保持的,而如果这个 fpm 进程是空闲的, 那么这个连接就是被浪费的。这有可能导致大量无用的连接占用 MySQL 的连接数, 而连接数是有上限的,超过之后就无法再建立新的连接, 导致后续的连接失败。所以必须设置长连接数的上限, 同时保证 worker 空闲一段时间后退出,(使用 pm.max_spare_servers
实现)或者再处理若干次请求之后重新启动(通过 pm.max_requests
实现), 以保证 MySQL 的正常连接数。
作者:happyhacker
链接:https://hacpai.com/article/1526490593632
来源:黑客派
协议:CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/