记一次结合PHP多进程和socket.io解决问题的经历
公司是做棋牌游戏的。前段时间接到一个后台人工鉴定并处理通牌作弊玩家的需求,其中需要根据几个玩家的游戏ID查询并计算他们在某段时间内彼此之间玩牌输赢次数和输赢总额。
牌局数据是存储在日志中心的,他们把牌局数据分成两个表来存储,一个表存储牌局概况数据,例如牌局时间、牌局ID、桌子ID、用户ID等信息,另一个表则存储每一个牌局的详情数据,例如,牌局有多少玩家参与,荷官在哪一轮发了什么牌,玩家每一轮都有什么动作等等。要想计算出几个玩家在某段时间之内玩牌输赢次数和输赢总额,就需要知道每一个牌局的详情数据,所以需要针对每一个玩家的游戏ID,先查询第一个表,查出所有牌局概况数据列表,然后遍历这个列表,根据每个牌局的牌局ID、桌子ID,从第二个表中查询每个牌局的详情数据,所有玩家的所有牌局详情数据都查询完成之后再进行统计。
日志中心的同学给出了查询以上两个表的接口,其中牌局详情的查询接口一次只能查询一个牌局的数据(和他们使用的数据表设计有关)。刚开始我的做法是在js代码中遍历所有给出的玩家ID,先查询出每个玩家的牌局列表,然后使用第二层循环来调用接口请求每一个牌局的详情数据,但这样做的问题是,有些用户在某段时间内的牌局数量是很大的,尽管控制了查询时间段的最大范围,但还是出现了一个用户几千个牌局的情况,这就意味着浏览器需要几乎在同一时间内对同一个域名的服务器发出几千个请求,而浏览器是基于域名进行并发控制的,超过限制数量的请求会被阻塞,阻塞严重的时候经常导致页面变成空白,好长时间才恢复,得到查询结果。这样的体验显然是不行的。
那怎么办呢?在老大的指导下,几经思虑,决定采用PHP多线程结合socket.io来完成这个任务。整体思路是这样的:首先js向PHP发起数据查询请求,PHP收到请求之后不是直接进行数据查询,而是在后台挂载一个进程去处理请求,然后返回一个确认状态值给js,这时js请求暂时结束了。这样做好处有二:其一,js请求的PHP接口是php-fpm运行的,使用php-fpm来fork多进程不太稳定,而使用php比较稳定;其二,可以避免数据查询过程时间太长导致超时。
挂载进程代码示例:
<?php
$par = ['startTime' => '', 'endTime' => '', 'mids' => $mid, ...];//牌局查询参数
$pKey = 'plog_proccess';//传给命令行的参数,作为进程标识,便于查询统计当前进程数量
$php = '/usr/local/php/bin/php';//php执行文件路径
$file = '/www/query.php';//牌局查询脚本文件
$cmd = $php.' '.$file.' '.$pKey.' '.base64_encode(serialize($par)).' > /dev/null 2>&1 &';//命令
system($cmd);//执行命令,挂载后台进程执行查询
接下来就要在进程运行的PHP脚本/www/query.php中进行数据查询了。首先遍历每一个玩家ID,查出每个玩家的所有牌局列表,然后遍历每个玩家的牌局列表,fork多个子进程进行每个牌局详情数据的查询了,一个子进程负责查询一个牌局的详情数据,并将数据写入文件中,代码示例如下:(注意:以下代码只是基本代码框架,无法直接运行)
<?php
$pKey = $argv[1];
$par = unserialize(base64_decode($argv[2]));
$mids = $par['mids'];
$max_pnum = 100;//最大子进程数量,避免抢占了过多的资源 for($mids as $mid) {//遍历查询各个用户的牌局数据
$list = ...;//这里进行当前用户牌局列表数据查询
$num = count($list);//牌局总数
$count = 0;//已有多少个牌局在查询 while(true) {//fork多个子进程查询各个牌局的详情数据
$s = "ps aux|awk '" . '/query.php/ && /' . $pKey . '/ && !/awk/' . "'|wc -l";
ob_start();
system($s);
$pNum = (int)ob_get_clean();//当前查询进程数量 if($count >= $num) {//当前牌局列表都已经交给各个子进程查询了
if($pnum > 1) {//有子进程没有完成,稍等
sleep(3);
continue;
} else {//所有子进程都已经完成,退出while循环,回到for循环中查询下一个用户的牌局数据
break;
}
} else if($pNum > $max_pnum) {//子进程数量超出限制,稍等
sleep(3);
continue;
} $rs = $list[$count];//从牌局列表中取出一个牌局来进行牌局详情数据查询
pcntl_signal(SIGCHLD, SIG_IGN);
$pid = pcntl_fork();//fork一个子进程,子进程会从此位置开始执行
if($pid < 0) {//子进程创建失败
//这里可以做一些日志记录 exit(0);
}
if($pid) {//子进程创建成功(主进程逻辑)
$count ++; } else if($pid == 0) {//进行牌局详情数据查询(子进程逻辑)
$pid = posix_setsid();//子进程ID
//这里根据$rs中的牌局数据进行牌局详情查询,并将得到的数据写入当前子进程专属文件(文件路径+文件名要唯一,可以使用时间戳、桌子ID和牌局ID组合表示) exit(0);//当前子进程任务完成,退出
}
}
}
exit(0);//查询完成,主进程退出
这个PHP后台挂载进程执行完成之后,所有需要查询的牌局数据就已经全部写入文件中了。现在问题来了,PHP应该怎么把这些数据传给前端页面呢?我们知道http协议是单向协议,只能由前端向服务器主动发起请求,而服务器是无法主动把数据发送给前端的,那怎么办呢?使用socket.io!可以在所有子进程执行完成之后,通过socket.io使用当前sock连接通知js,js收到消息之后即发送请求给一个PHP接口,这个PHP接口的任务便是读取上述多进程在文件中写下的数据,返回给js进行页面渲染。
关于socket.io,没有进行过多研究,使用的是公司框架封装好的,当然也可以使用原生的,简单教程地址:http://www.workerman.net/phpsocket_io,这里只是简单介绍一下思路。
首先需要到上面这个地址中下载phpsocket,然后启动一个服务端,注意,只能在命令行中启动,同样可以作为一个后台挂载进程运行。
<?php
require_once __DIR__ . '/socketio/vendor/autoload.php';
use Workerman\Worker;
use PHPSocketIO\SocketIO; //创建socket.io服务器,监听2021端口
$io = new SocketIO(2021); //向客户端发送消息,通知数据已查询完成
$io->emit('hello', json_encode([1 => 'hello', 'aaa' => 'ewfewr'])); Worker::runAll();
然后在客户端js中监听这个消息:
<script src='https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js'></script>
<script>
var socket = io('http://127.0.0.1:2021');
socket.on('hello', function(par){
//这里便是发送请求到PHP接口进行数据读取了
});
</script>
若是觉得使用原生socket.io麻烦,也可以使用封装好的ElephantIO。
当然,这里有个问题,就是写数据产生的文件会越来越多,可以在每次挂载进程进行写文件之前先把之前写的文件(已经没用了的)进行删除:
function rmDataDir($dir) {
if(!is_dir($dir)) return; $handle = opendir($dir);
while($file = readdir($handle)) {
if(in_array($file, ['.', '..'])) continue; $str = $dir . $file;
if(is_dir($str)) {
rmDataDir($str . '/');
} else {
unlink($str);
}
}
closedir($handle);
$arr = scandir($dir);//readdir()有时候没有识别完所有文件就返回false了。。。
if(count($arr) <= 2) {//只有.和..的时候可以删除
rmdir($dir);
}
}
同时,由于在这个功能中,每次发送查询数据请求的代价都是比较昂贵的,可以考虑在js中对查询过的数据进行缓存,例如,相同查询条件下相同用户ID,已经查询过的就不需要查询了,直接从js缓存中读取数据进行页面渲染就可以了。
然而,尽管使用了PHP多进程,但是进行了很多的文件读写操作,磁盘IO也是很耗时间的,所以速度上并没有提升多少,只是不会再出现浏览器页面卡死的情况了。这个功能中关于速度的提升不知还有什么更好的方法呢???各位朋友,走过路过,别忘了给下建议哈~
记一次结合PHP多进程和socket.io解决问题的经历的更多相关文章
- NodeJs多进程和socket.io通讯-DEMO
一.开启多进程 const os = require('os'); const cp = require('child_process'); const forkList = {}; const fo ...
- 在web浏览器上显示室内温度(nodeJs+arduino+socket.io)
上次的nodejs操作arduino入门篇中实现了如何连接arduino.这次我们来实现通过arduino测量室内温度并在浏览器上显示出来. [所需材料] 硬件:LM35温度传感器,arduino u ...
- 使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程(三)
这几篇都是我原来首发在 segmentfault 上的地址:https://segmentfault.com/a/1190000005040834 突然想起来我这个博客冷落了好多年了,也该更新一下,呵 ...
- socket.io搭配pm2(cluster)集群解决方案
socket.io与cluster 在线上系统中,需要使用node的多进程模型,我们可以自己实现简易的基于cluster模式的socket分发模型,也可以使用比较稳定的pm2这样进程管理工具.在常规的 ...
- socket.io emit callback调用探秘
socket.io https://socket.io/ https://socket.io/docs/ What Socket.IO is Socket.IO is a library that e ...
- 基于node.js+socket.io+html5实现的斗地主游戏(1)概述
一.游戏描述 说是斗地主游戏,其实是寝室自创的"捉双A",跟很多地方的捉红10.打红A差不多,大概规则是: 1.基础牌型和斗地主一样,但没有大小王,共52张牌,每人13张,这也是为 ...
- Node中的Socket.IO 简单Demo及说明
注:下面Demo的Server和Client都是纯后端. 并没有web页面. Server端代码: var express = require('express'); var app = expres ...
- 多线程,多进程和异步IO
1.多线程网络IO请求: #!/usr/bin/python #coding:utf-8 from concurrent.futures import ThreadPoolExecutor impor ...
- 即时通信WebSocket 和Socket.IO
WebSocket HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯. 在2008年诞生,2011年成为国际标准. 现在基本所有浏览器都已经支持了. We ...
随机推荐
- centos安装软件依赖问题
yum install gcc gcc-c++ ncurses-devel perl 基础包安装
- c++实现一个小算法
题目:有n个格子,每个格子里有坦克,坦克有两滴血,你向格子里投掷炸弹,每次命中坦克他掉一滴血并随机像左或者右移动一个格子,问最少炸几次能把全部坦克炸完. 题解:先向偶数格子投掷炸弹,所有的坦克全跑到奇 ...
- the c programing language 学习过程6
payroll工资名单 hierarchy分层层次 vexing 使人烦恼的 alignment结盟 semantics 语义 aethetic审美 parameterize 参数化 1结构标记 成员 ...
- HDU - 2187 贪心
思路: 按照单价升序排序,依次买就行. AC代码 #include <cstdio> #include <cmath> #include <algorithm> ...
- hdu1242 Rescue bfs+优先队列
直接把Angle的位置作为起点,广度优先搜索即可,这题不是步数最少,而是time最少,就把以time作为衡量标准,加入优先队列,队首就是当前time最少的.遇到Angle的朋友就退出.只需15ms A ...
- HDP2.0.6+hadoop2.2.0+eclipse(windows和linux下)调试环境搭建
花了好几天,搭建好windows和linux下连接HDP集群的调试环境,在此记录一下 hadoop2.2.0的版本比hadoop0.x和hadoop1.x结构变化很大,没有eclipse-hadoop ...
- Ubuntu16.04安装搜狗输入法后有黑边问题的解决方法
apt-get install compton compton -b
- R语言︱ROC曲线——分类器的性能表现评价
笔者寄语:分类器算法最后都会有一个预测精度,而预测精度都会写一个混淆矩阵,所有的训练数据都会落入这个矩阵中,而对角线上的数字代表了预测正确的数目,即True Positive+True Nagetiv ...
- freemarker处理哈希表的内建函数
freemarker处理哈希表的内建函数 1.简易说明 (1)map取值 (2)key取值 2.实现示例 <html> <head> <meta http-equiv=& ...
- freemarker中的split字符串分割
freemarker中的split字符串分割 1.简易说明 split分割:用来根据另外一个字符串的出现将原字符串分割成字符串序列 2.举例说明 <#--freemarker中的split字符串 ...