這次帶給大家PHP讀寫檔高並發處理操作步驟分析,PHP讀寫檔高並發處理的注意事項有哪些,下面就是實戰案例,一起來看一下。
最近公司遊戲開發需要知道遊戲載入的流失率。因為,我們做的是網頁遊戲。玩過網頁遊戲的人都知道,進入遊戲前要先載入一些資源。最後才能到達創造角色的遊戲介面。我們有一個需求就是要統計在載入過程中還未到達角色建立介面而流失的使用者數量。
我們在載入開始就進行統計人數,載入完成之後再記錄人數。這樣,透過用加載前的人數減去成功加載後的人數。就知道了加載的流失率。就可以知道遊戲是否還要繼續優化載入過程,降低用戶載入遊戲率。
由於,我們的量都是從*主流的合作媒體進行導量過來。所以,並發非常高,據粗略計算應該可以達到每秒1000左右的並發數量。
載入前的人數本來想放到遊戲內部的快取平台。但是,遊戲後端的同事擔心並發太高,導致資源無故浪費。因為,記憶體的釋放並不是即時回應的。所以,將統計的人數放到在另外一台伺服器:統計伺服器。
我剛開始採用的方案如下:
透過php的<a href="http://www.php.cn/wiki/1311.html" target="_blank">file_get_contents</a>()
與<a href="http://www.php.cn/wiki/1312.html" target="_blank">file_put_contents</a>( )
進行讀取與寫入。第一次讀寫就向文件寫入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中文網其他相關文章!