公司是做棋牌游戏的。前段时间接到一个后台人工鉴定并处理通牌作弊玩家的需求,其中需要根据几个玩家的游戏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. mysql sql_cache缓存使用

    有如下规则,如果数据表被更改,那么和这个数据表相关的全部Cache全部都会无效,并删除之.这里"数据表更改"包括: INSERT, UPDATE, DELETE, TRUNCATE ...

  2. angular2^ typescript 将 文件和Json数据 合并发送到服务器(2.服务端)

    nodejs 中使用框架 express web框架 multer 文件接受 直接贴代码了,我就不解释了 "use strict"; exports.__esModule = tr ...

  3. Python世界里的赋值运算符

    Python赋值运算符 以下假设变量a为10,变量b为20: "=" 的作用是把右边的数值赋值给左边的变量 示例1:编程实现145893秒是几天几小时几分钟几秒钟? total = ...

  4. 应用负载均衡之LVS(三):使用ipvsadm以及详细分析VS/DR模式

    */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...

  5. 【剑指offer28:字符串的排列】【java】

    题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. import ja ...

  6. 真实场景的双目立体匹配(stereo matching)以及虚拟视点合成(virtual view synthsis)示例

    双目立体匹配一直是双目视觉的研究热点,双目相机拍摄同一场景的左.右两幅视点图像,运用立体匹配匹配算法获取视差图,进而获取深度图.而深度图的应用范围非常广泛,由于其能够记录场景中物体距离摄像机的距离,可 ...

  7. 对于ArrayList中的泛型进行分析

    package cn.lonecloud.reflect; import java.lang.reflect.Method; import java.util.ArrayList; public cl ...

  8. 【推荐】免费,19 款仿 Bootstrap 后台管理主题下载

    声明: 1. 本篇文章提到的仿 Bootstrap 风格的主题,是基于 jQuery 的 ASP.NET MVC 控件库的主题. 2. FineUIMvc(基础版)完全免费,可以用于商业项目. 目录 ...

  9. caffe︱深度学习参数调优杂记+caffe训练时的问题+dropout/batch Normalization

    一.深度学习中常用的调节参数 本节为笔者上课笔记(CDA深度学习实战课程第一期) 1.学习率 步长的选择:你走的距离长短,越短当然不会错过,但是耗时间.步长的选择比较麻烦.步长越小,越容易得到局部最优 ...

  10. Windows下基于ADS+J-Link 的ARM开发环境搭建

    在一般ARM编程教学和实验环境里,一般采用 ADS加+并口转Jtag板+H-Jtag的开发环境.但是这种方法最大缺点是需要机器上有一个并口.现在无论PC还是笔记本都很难有并口,因此采用USB接口调试器 ...