一. 概述
项目是棋牌, web 架构是典型的 lnmp,server 产生的牌局通过 http 协议请求 webserver, 由 php 分析并持久化到 mysql, 中间参杂了很多业务逻辑, 整个流程耗时平均接近 2s. 这种方式存在以下 2 个问题 1. 整个流程是同步的, server 会一直等待 php 响应, 一旦 server 处理不慎, 会造成 server 阻塞, 玩家无法玩牌. 2. 如果牌局数量较多, 会占用较多的 php-fpm 进程, 可能造成 php-fpm 无法处理其他业务.
二. 改进方式
后面改由 server 把牌局数据写到 redis 队列里, php 使用守护进程处理 redis 队列. cron 每 5 分钟运行 gamelog.php,gamelog 检测牌局队列数量, 根据队列的数量动态 fork 对应的子进程处理牌局业务, 当子进程数量有多余的空闲进程, gamelog.php 会杀掉多余的进程, 这种方式参考了 php-fpm 的 dynamic 模式, 具体实现如下:
- define('LEN', 50);// 单进程处理牌局队列长度
- define('PROC_MIN', 2);// 最小进程数
- define('PROC_MAX', 5);// 最大进程数
- ini_set('memory_limit', '128M');
- umask(002);
- set_time_limit(0);//cli 模式非必须
- $daemonNum = (int) `ps -ef | grep "gamelog.php" | grep -v grep | awk '$3 == 1 {print $2}' | wc -l`;// 当前守护进程数
- $appGameLog = app::gamelog();
- $appRedis = app::redis('log');
- $key = akey::gamelog();
- $len = $appRedis->lSize($key);
- $wokerNum = ceil($len / LEN);// 需要的进程数
- ($wokerNum <PROC_MIN) && ($wokerNum = PROC_MIN);
- if($daemonNum < $wokerNum){// 守护进程数小于需要开启的进程数
- $procNum = $wokerNum - $daemonNum;
- $procNum = min($procNum, PROC_MAX);
- $procNum = max($procNum, PROC_MIN);
- for($p = 1; $p <= $procNum; $p++){
- $pid = pcntl_fork();
- if($pid == 0){
- posix_setsid();
- while(true){
- $data = $appRedis->rPop($key);
- // 此处处理业务
- }
- exit;
- }else if($pid> 0){
- }else{
- exit("fork error");
- }
- }
- }else if($daemonNum> $wokerNum){// 进程数过多自行 kill
- $pidStr = `ps -ef | grep "gamelog.php" | grep -v grep | awk '$3 == 1 {print $2}'`;
- $aPid = explode(PHP_EOL, $pidStr);
- $wokerNum = max($wokerNum, PROC_MIN);// 最少保留 PROC_MIN 个守护进程
- $killNum = $daemonNum - $wokerNum;
- foreach($aPid as $pid){
- $pid = (int) $pid;
- if($pid <= 0){
- continue;
- }
- if(posix_kill($pid, SIGKILL)){
- if(--$killNum <= 0){
- break;
- }
- }
- }
- }
php 执行 shell 命令除了 system(),exec(), 还可以使用 ``.posix_setsid() 函数 php 手册里只有一句说明 Make the current process a session leaderposix_setsid 对应的 unix 系统函数是 setsid(), 当进程调用 setsid 会产生一个新的会话, 而且这个进程将不受终端控制之前进程有终端控制也会被解除, 所以我们在命令行启动 gamelog.php, 然后关掉终端不会杀掉 gamelog.php 产生的子进程
三. 改进后的效果 1. 改进后 server 写 redis 队列远比通过 http 协议请求 php 快, 极大减少了 server 等待牌局处理的时间. 2.php-fpm 不用处理牌局的请求, 改由后台进程处理, 释放了 php-fpm.
来源: https://www.cnblogs.com/gaoqin31/p/9550400.html