公司是做棋牌游戏的。前段时间接到一个后台人工鉴定并处理通牌作弊玩家的需求,其中需要根据几个玩家的游戏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解决问题的经历的更多相关文章

  1. NodeJs多进程和socket.io通讯-DEMO

    一.开启多进程 const os = require('os'); const cp = require('child_process'); const forkList = {}; const fo ...

  2. 在web浏览器上显示室内温度(nodeJs+arduino+socket.io)

    上次的nodejs操作arduino入门篇中实现了如何连接arduino.这次我们来实现通过arduino测量室内温度并在浏览器上显示出来. [所需材料] 硬件:LM35温度传感器,arduino u ...

  3. 使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程(三)

    这几篇都是我原来首发在 segmentfault 上的地址:https://segmentfault.com/a/1190000005040834 突然想起来我这个博客冷落了好多年了,也该更新一下,呵 ...

  4. socket.io搭配pm2(cluster)集群解决方案

    socket.io与cluster 在线上系统中,需要使用node的多进程模型,我们可以自己实现简易的基于cluster模式的socket分发模型,也可以使用比较稳定的pm2这样进程管理工具.在常规的 ...

  5. socket.io emit callback调用探秘

    socket.io https://socket.io/ https://socket.io/docs/ What Socket.IO is Socket.IO is a library that e ...

  6. 基于node.js+socket.io+html5实现的斗地主游戏(1)概述

    一.游戏描述 说是斗地主游戏,其实是寝室自创的"捉双A",跟很多地方的捉红10.打红A差不多,大概规则是: 1.基础牌型和斗地主一样,但没有大小王,共52张牌,每人13张,这也是为 ...

  7. Node中的Socket.IO 简单Demo及说明

    注:下面Demo的Server和Client都是纯后端. 并没有web页面. Server端代码: var express = require('express'); var app = expres ...

  8. 多线程,多进程和异步IO

    1.多线程网络IO请求: #!/usr/bin/python #coding:utf-8 from concurrent.futures import ThreadPoolExecutor impor ...

  9. 即时通信WebSocket 和Socket.IO

    WebSocket HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯. 在2008年诞生,2011年成为国际标准. 现在基本所有浏览器都已经支持了. We ...

随机推荐

  1. Xcode intellisense meaning of letters in colored boxes like f,T,C,M,P,C,K,# etc

    in Xcode this is called "Code Sense". And these icons also exist in Xcode 3. Red: macros # ...

  2. word在页眉中插入页码

    编辑页眉时,插入-页码-当前位置-普通数字

  3. 同步博客—CSDN推广

    niiickのCSDN 用CSDN也有几个月了 其实一开始有人让我转到博客园我是拒绝的 (毕竟强迫症接受不了一边博客只有一半= =) 不过最近有幸观赏了某位dalao的博客园 发现没有广告好棒!!!设 ...

  4. Android -传统蓝牙通信聊天

    概述 Android 传统蓝牙的使用,包括开关蓝牙.搜索设备.蓝牙连接.通信等. 详细 代码下载:http://www.demodashi.com/demo/10676.html 原文地址: Andr ...

  5. Linux下 开启防火墙端口

    命令行输入: vi /etc/sysconfig/iptables 将 -A INPUT -m state --state NEW -m tcp -p tcp --dport 端口号 -j ACCEP ...

  6. NJU 1010 Air

    思路:把那张图打表(吐血...),然后就按照规则输出就行. AC代码 #include <cstdio> #include <cmath> #include <cctyp ...

  7. linux kvm虚拟机快速构建及磁盘类型

    KVM命令管理 virsh命令:用来管理各虚拟机的接口命令查看/创建/停止/关闭...支持交互模式格式:virsh 控制指令 [虚拟机名称] [参数] [root@room1pc01 桌面]# vir ...

  8. (2018干货系列五)最新UI设计学习路线整合

    怎么学UI全链路设计 全链路设计师是参与整个商业链条,为每个会影响用户体验的地方提供设计的可解决方案,最后既满足了商业目标,又提升了产品的用户体验和设计质量,与平面设计.UI设计彻底区分开来,是真正的 ...

  9. 工作中常用的linux命令(2)

    1.find :查找指定文件名的路径: 列出当前目录以及子目录中的所有文件: 在当前目录下寻找特定文件名的文件: 列出长度为零的文件: 2.ps :查看某个程序的进程,例如查询mongodb和mysq ...

  10. ssh禁止密码登录

    1.root用户登陆后,运行以下第一句指令,其他根据提示进行输入: ssh-keygen -t rsaGenerating public/private rsa key pair.Enter file ...