我刚开始采用的方案如下:
通过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写文件日志丢失
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); } } } }}
苹果手机删除了存储的手机号联系人,该怎么恢复数据呢? - 网络| | 苹果手机删除了存储的手机号联系人,该怎么恢复数据呢? - 网络| ...