相關(guān)關(guān)鍵詞
關(guān)于我們
最新文章
PHP讀寫文件高并發(fā)處理操作實例詳解
本文實例講述了PHP讀寫文件高并發(fā)處理操作。分享給大家供大家參考,具體如下:
背景:
最近公司游戲開發(fā)需要知道游戲加載的流失率。因為,我們做的是網(wǎng)頁游戲。玩過網(wǎng)頁游戲的人都知道,進入游戲前要加載一些資源。最后才能到達創(chuàng)建角色的游戲界面。我們有一個需求就是要統(tǒng)計在加載過程中還未到達角色創(chuàng)建界面而流失的用戶數(shù)量。
我們在加載開始就進行統(tǒng)計人數(shù),加載完成之后再記錄人數(shù)。這樣,通過用加載前的人數(shù)減去成功加載后的人數(shù)。就知道了加載的流失率。就可以知道游戲是否還要繼續(xù)優(yōu)化加載過程,降低用戶加載游戲率。
由于,我們的量都是從*主流的合作媒體進行導量過來。所以,并發(fā)非常高,據(jù)粗略計算應該能達到每秒1000左右的并發(fā)數(shù)量。
加載前的人數(shù)本來想放到游戲內(nèi)部的緩存平臺。但是,游戲后端的同事?lián)牟l(fā)太高,導致資源無故浪費。因為,內(nèi)存的釋放并不是實時響應的。所以,將統(tǒng)計的人數(shù)放到在另外一臺服務器:統(tǒng)計服務器。
我剛開始采用的方案如下:
通過php的file_get_contents()
與file_put_contents()
進行讀取與寫入。第一次讀寫就向文件寫入1,第二次加載就在原來的基礎上加1.以此類推.這種順序的思想完全不存在任何問題。問題就出在,我們的服務器不可能是順序形式的。
準確的說,并發(fā)的訪問不是順序的。當A玩家加載游戲讀取到文件里面的數(shù)字100(假如這時是100),B玩家讀取到的也是100,這時,處理A玩家的線程就是在100的基礎上加1,得到101,就會向文件寫入101。
處理B玩家的線程也得到相同的結(jié)果,將101寫入文件。這時,問題就出現(xiàn)了?B玩家是在A玩家之后加載游戲的,理應得到102的計算結(jié)果。
這就是并發(fā)導致的問題。這個時候,我想到了采用fopen()
打開文件,并用flock()
加一個寫入鎖。大家一定會認為,這種方式有了鎖定,那么就不會造成問題了。其實,也是錯的。
因為,我們的問題不是出在寫入上面。而是讀取的時候造成數(shù)據(jù)的不同步。OK。到這里,我實在百度谷歌都搞不定了。
當希望寄托在PHP函數(shù)本身而夢碎的時候,我只能另尋它法。脫離它。于是,我想到了*語言的Map映射的機制。類似于我們的PHP數(shù)組,每加載一次就我往數(shù)組添加一個元素。這樣,到最后我只需要count()
一下數(shù)組就知道了有多少玩家加載了游戲。
但是,用數(shù)組的話,也存在一個問題。就是PHP的變量還是常量,在腳本執(zhí)行完畢之后都會自己清掉。于是,我想到了文件保存的方式。
最終的可行方案思路如下:
用fopen打開一個文件,以只寫的方式。然后寫鎖定。玩家每加載一次我就向文件里面寫入一個數(shù)字1,最后得到的文件內(nèi)容通過file_get_contents()
一次性讀取出來,再用strlen()
計算一下長度即知道了有多少玩家加載了游戲。
聽聞flock()
函數(shù)會鎖定會造成系統(tǒng)資源在很多時間升高。所以,我采用大家所使用的方式,用微秒超時的技術(shù)解決這個問題。如果,走出這個時間我就*掉它。具體的代碼如下:
// loadcount.func.php 函數(shù)文件。 /** * 獲取某來源和某服務器ID的游戲加載次數(shù)。 * * @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); } /** * 獲取某來源所有服務器的游戲加載次數(shù)。 * * @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; } /** * 清空所有的加載數(shù)據(jù)。 * * @return void */ function clearLoadCount() { foreach (glob("*.txt") as $filename) { unlink($filename); } return true; } /** * 延遲更新游戲加載次數(shù)中間件。 * * 使用此函數(shù)來延遲更新數(shù)據(jù),原理:當不足1000次的時候,不更新數(shù)據(jù)庫,超過1000就更新到數(shù)據(jù)庫里面去。 * * @param string $fromid 來源標識。 * @param int $serverid 服務器ID編號。 */ function delayAddLoadCount($fromid, $serverid) { // 使用MD5生成文件名記錄緩存次數(shù)。 $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; }