在PHP中,访问MySQL数据库往往是性能提升的瓶颈。而MySQL连接池我想大家都不陌生,这是一个很好的提升数据库访问性能的方式。传统的MySQL连接池,是预先申请一定数量的连接,每一个新的请求都会占用其中一个连接,请求结束后再将连接放回池中,如果所有连接都被占用,新来的连接则会进入等待状态。
知道了MySQL连接池的实现原理,再来看如何使用Swoole实现一个连接池。
首先,Swoole允许开启一定量的Task Worker进程,我们可以让每个进程都拥有一个MySQL连接,并保持这个连接,这样,我们就创建了一个连接池。
其次,设置swoole的dispatch_mode为抢占模式(主进程会根据Worker的忙闲状态选择投递,只会投递给处于闲置状态的Worker)。这样,每个task都会被投递给闲置的Task Worker,保证了每个新的task都会被闲置的Task Worker处理。如果全部Task Worker都被占用,则会进入等待队列。
mysql连接默认等待时间(wait_timeout)是28800秒(8个小时),也就是说客户端建立一个连接,没有主动断开连接,也没有任何操作的情况下,MySQL会在到达设置的wait_timeout后断开这个连接。
问题由此产生,由于Swoole常驻内存,进程一直存在,如果使用Swoole异步处理数据,可能会出现长时间未发送数据给Swoole服务端,若此异步中存在MySQL相关操作,就可能出现因超过MySQL的wait_timeout等待时间,而被强制关闭连接的情况。
此时查看Swoole进程是正常的,但程序却不能正常执行,守护模式下若打印了日志,可能看到如下错误:
ERROR php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Uncaught mysqli_sql_exception: MySQL server has gone away in...
解决此问题,可在程序中加入心跳检测机制,定时操作MySQL,使其一直保持连接状态。
Swoole的TCP服务内置了心跳检测功能,只需要设置 heartbeat_check_interval 和 heartbeat_idle_time 即可开启。但这种心跳检测仅面向客户端请求。
'heartbeat_idle_time' => 60, //表示一个连接如果600秒内未向服务器发送任何数据,此连接将被强制关闭
'heartbeat_check_interval' => 10 //表示每60秒遍历一次
对于服务来说,最好的办法是手动创建一个心跳检测机制。
原理比较简单,即做一个定时器,定时发送请求,保持MySQL连接,在启动Swoole的时候,同时启动定时器。
定时器要单独开启一个进程,否则会和单前的HTTP服务器冲突,并发出警告:
PHP Warning: Swoole\Server::start(): eventLoop has already been created, unable to start Swoole\Http\Server
正确做法:
use Swoole\Process;
$http = new Swoole\Http\Server("0.0.0.0", 9501);
//创建子进程
$process = new Process(function () {
//默认定时器在执行回调函数时会自动创建协程
Swoole\Timer::tick(2000, function (int $timer_id) { //单位:毫秒
echo "coro-----1 " . Coroutine::getcid() . " start\n";
});
});
$http->addProcess($process);
使用class的方式:
public function __construct(){
$this->db_factroy = new db_factroy();
$this->serv = new Swoole\Http\Server("127.0.0.1", 3311);
$this->serv->set([
'worker_num' => 4,
'max_request' => 1,
'task_worker_num' => 50,
'daemonize' => $this->daemonize,
'log_file' => $_SERVER['DOCUMENT_ROOT'].'caches/swoole_logs' . '/'.date('Ymd').'.log'
]);
$this->serv->on('Request', [$this, 'onRequest']);
$this->serv->on("Task", [$this, 'onTask']);
$this->serv->on("Finish", [$this, 'onFinish']);
$this->serv->on("WorkerStart", [$this, 'onWorkerStart']);
//创建子进程,创建定时器(心跳检测)
$this->process = new Process(function(){
Swoole\Timer::tick(7200000, function() {
$this->Connection();
});
});
$this->serv->addProcess($this->process);
$this->serv->start();
}
//心跳检测
public function Connection(){
$this->log_name = date('Ymd');
$this->db_factroy->select("","aid","aid desc","1","user");
}
时间原因,先记录这些。
参考文章:
https://blog.csdn.net/sunrj_niu/article/details/129706148
https://blog.csdn.net/weixin_33923762/article/details/92145939
https://developer.aliyun.com/article/1547450