未加星标

PHP读写文件高并发处理操作实例详解

字体大小 | |
[开发(php) 所属分类 开发(php) | 发布者 店小二05 | 时间 | 作者 红领巾 ] 0人收藏点击收藏

本文实例讲述php读写文件高并发处理操作。分享给大家供大家参考,具体如下:

背景:

最近公司游戏开发需要知道游戏加载的流失率。因为,我们做的是网页游戏。玩过网页游戏的人都知道,进入游戏前要加载一些资源。最后才能到达创建角色的游戏界面。我们有一个需求就是要统计在加载过程中还未到达角色创建界面而流失的用户数量。

我们在加载开始就进行统计人数,加载完成之后再记录人数。这样,通过用加载前的人数减去成功加载后的人数。就知道了加载的流失率。就可以知道游戏是否还要继续优化加载过程,降低用户加载游戏率。

由于,我们的量都是从*主流的合作媒体进行导量过来。所以,并发非常高,据粗略计算应该能达到每秒1000左右的并发数量。

加载前的人数本来想放到游戏内部的缓存平台。但是,游戏后端的同事担心并发太高,导致资源无故浪费。因为,内存的释放并不是实时响应的。所以,将统计的人数放到在另外一台服务器:统计服务器。

我刚开始采用的方案如下:

通过php的file_get_contents()与file_put_contents()进行读取与写入。第一次读写就向文件写入1,第二次加载就在原来的基础上加1.以此类推.这种顺序的思想完全不存在任何问题。问题就出在,我们的服务器不可能是顺序形式的。

准确的说,并发的访问不是顺序的。当A玩家加载游戏读取到文件里面的数字100(假如这时是100),B玩家读取到的也是100,这时,处理A玩家的线程就是在100的基础上加1,得到101,就会向文件写入101。

处理B玩家的线程也得到相同的结果,将101写入文件。这时,问题就出现了?B玩家是在A玩家之后加载游戏的,理应得到102的计算结果。

这就是并发导致的问题。这个时候,我想到了采用fopen()打开文件,并用flock()加一个写入锁。大家一定会认为,这种方式有了锁定,那么就不会造成问题了。其实,也是错的。

因为,我们的问题不是出在写入上面。而是读取的时候造成数据的不同步。OK。到这里,我实在百度谷歌都搞不定了。

当希望寄托在PHP函数本身而梦碎的时候,我只能另寻它法。脱离它。于是,我想到了*语言的Map映射的机制。类似于我们的PHP数组,每加载一次就我往数组添加一个元素。这样,到最后我只需要count()一下数组就知道了有多少玩家加载了游戏。

但是,用数组的话,也存在一个问题。就是PHP的变量还是常量,在脚本执行完毕之后都会自己清掉。于是,我想到了文件保存的方式。

最终的可行方案思路如下:

用fopen打开一个文件,以只写的方式。然后写锁定。玩家每加载一次我就向文件里面写入一个数字1,最后得到的文件内容通过file_get_contents()一次性读取出来,再用strlen()计算一下长度即知道了有多少玩家加载了游戏。

听闻flock()函数会锁定会造成系统资源在很多时间升高。所以,我采用大家所使用的方式,用微秒超时的技术解决这个问题。如果,走出这个时间我就*掉它。具体的代码如下:

// loadcount.func.php 函数文件。
/**
* 获取某来源和某服务器ID的游戏加载次数。
*
* @param string $fromid 来源标识。
* @param int $serverid 服务器ID编号。
*
* @return int
*/
function getLoadCount($fromid, $serverid)
{
global $g_global;
$serverid = (int) $serverid;
$fromid = md5($fromid);
$filename = $fromid . $serverid . '.txt';
$data = file_get_contents($filename);
return strlen($data);
}
/**
* 获取某来源所有服务器的游戏加载次数。
*
* @param string $fromid 来源标识。
*
* @return int
*/
function getAllLoadCount($fromid)
{
global $g_global;
$fromid = md5($fromid);
$count = 0;
foreach (glob("{$fromid}*.txt") as $filename)
{
$file_content = file_get_contents($filename);
$count += strlen($file_content);
}
return $count;
}
/**
* 清空所有的加载数据。
*
* @return void
*/
function clearLoadCount()
{
foreach (glob("*.txt") as $filename) {
unlink($filename);
}
return true;
}
/**
* 延迟更新游戏加载次数中间件。
*
* 使用此函数来延迟更新数据,原理:当不足1000次的时候,不更新数据库,超过1000就更新到数据库里面去。
*
* @param string $fromid 来源标识。
* @param int $serverid 服务器ID编号。
*/
function delayAddLoadCount($fromid, $serverid)
{
// 使用MD5生成文件名记录缓存次数。
$fromid = md5($fromid);
$filename = $fromid . $serverid . '.txt';
if($fp = fopen($filename, 'a'))
{
$startTime = microtime();
do {$canWrite = flock($fp, LOCK_EX);if(!$canWrite){ usleep(round(mt_rand(0, 100)*1000));}
}
while ( ( !$canWrite ) && ( ( microtime()- $startTime ) < 1000 ) );
if ($canWrite)
{fwrite($fp, "1");
}
fclose($fp);
}
return true;
}

以下是我调用以上方法的文件:

< ?php
/**
* @describe 平台用户加载游戏次数统计接口入口。
* @date 2012.12.17
*/
include_once './loadcount.func.php';
// 测试用。
// $_GET['fromid'] = '4399';
// $_GET['serverid'] = mt_rand(0, 5);
// 添加加载次数。
if ( $_GET['action'] == 'addcount' )
{
$fromid = $_GET['fromid']; // 来源标识。
$serverid = $_GET['serverid']; // 服务器ID编号。
$return = delayAddLoadCount($fromid, $serverid);
$return = $return ? 1 : 0;
ob_clean();
echo json_encode($return);
exit;
}
// 取加载次数。
elseif ( $_GET['action'] == 'getcount' )
{
$fromid = $_GET['fromid']; // 来源标识。
if ( !isset( $_GET['serverid'] ) ) // 有服务器编号 ID则取来源对应的服务器加载次数。
{
$count = getAllLoadCount($fromid);
}
else // 加载对应来源的次数。
{
$serverid = $_GET['serverid']; // 服务器ID编号。
$count = getLoadCount($fromid, $serverid);
}
ob_clean();
header('Content-Type:text/html;charset=UTF-8');
$serverid = strlen($serverid) ? $serverid : '无';
echo "来源:{$fromid},服务器ID:{$serverid},游戏加载次数:" . $count;
exit;
}
// 清除加载次数。
elseif ( $_GET['action'] == 'clearcount' )
{
header('Content-Type:text/html;charset=UTF-8');
$return = clearLoadCount();
if ($return)
{
echo "清除成功!";
}
else
{
echo "清除失败!";
}
}

这是血的教训,所以,我不得不将它记录下来。以备以后让他人借鉴。

本文是作者寒冰一年前在4399游戏工作室负责做数据分析的时候写的代码。希望对大家有所帮助。

PHP数据库操作之高并发实例

高并发下PHP写文件日志丢失

<?php
/**
* Created by PhpStorm.
* User: andyfeng
* Date: 2015/6/24
* Time: 13:31
*/
class LogFileUtil {
public static $fileHandlerCache;
private static $initFlag = false;
private static $MAX_LOOP_COUNT = 3;
private static function init() {
self::$initFlag = true;
register_shutdown_function(array("LogFileUtil", "shutdown_func"));
}
/**
* 输出到文件日志
* @param $filePath 文件路径
* @param $msg 日志信息
* @return int
*/
public static function out($filePath, $msg) {
if (!self::$initFlag) {
self::init();
}
return self::internalOut($filePath, $msg);
}
/**
* @param $filePath
* @param $msg
* @param $loop
* @return int
*/
private static function internalOut($filePath, $msg, $loop = 0) {
//以防一直添加失败造成死循环
if ($loop > self::$MAX_LOOP_COUNT) {
$result = 0;
} else {
$loop++;
$fp = self::$fileHandlerCache["$filePath"];
if (empty($fp)) {
$fp = fopen($filePath, "a+");
self::$fileHandlerCache[$filePath] = $fp;
}
if (flock($fp, LOCK_EX)) {
$result = fwrite($fp, $msg);
flock($fp, LOCK_UN);
} else {
$result = self::internalOut($filePath, $msg, $loop);
}
}
return $result;
}
function shutdown_func() {
if (!empty(LogFileUtil::$fileHandlerCache)) {
if (is_array(LogFileUtil::$fileHandlerCache)) {
foreach (LogFileUtil::$fileHandlerCache as $k => $v) {
if (is_resource($v))//file_put_contents("close.txt",$k);fclose($v);
}
}
}
}
}

更多关于PHP相关内容感兴趣的读者可查看本站专题:《php文件操作总结》、《php缓存技术总结》、《PHP运算与运算符用法总结》、《PHP基本语法入门教程》、《php面向对象程序设计入门教程》、《php字符串(string)用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》

希望本文所述对大家PHP程序设计有所帮助。


您可能感兴趣的文章:php处理抢购类功能的高并发请求php结合redis实现高并发下的抢购、秒杀功能的实例php 根据url自动生成缩略图并处理高并发问题PHP flock 文件锁详细介绍PHP程序中的文件锁、互斥锁、读写锁使用技巧解析PHP文件锁定写入实例解析PHP文件锁函数flock()详细介绍PHP 文件锁与进程锁的使用示例PHP基于文件锁解决多进程同时读写一个文件问题示例PHP使用文件锁解决高并发问题示例

本文开发(php)相关术语:php代码审计工具 php开发工程师 移动开发者大会 移动互联网开发 web开发工程师 软件开发流程 软件开发工程师

tags: fromid,加载,serverid,PHP,return,php,文件,fp,游戏,GET,param,filePath,function,并发
分页:12
转载请注明
本文标题:PHP读写文件高并发处理操作实例详解
本站链接:http://www.codesec.net/view/574942.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(php) | 评论(0) | 阅读(20)