今回は、PHP ファイルの読み取りと書き込みの高同時処理の動作手順の分析と、PHP ファイルの読み取りと書き込みの高同時処理の 注意事項について説明します。以下は実際のケースです。見てください。 最近、会社のゲーム開発では、ゲーム読み込みのチャーンレートを知る必要があります。私たちはウェブゲームを作っているからです。 Web ゲームをプレイしたことがある人なら、ゲームに入る前にいくつかのリソースをロードする必要があることを知っています。最後に、キャラクターを作成するためのゲームインターフェイスに到達できます。私たちのニーズの 1 つは、ロード プロセス中にキャラクター作成インターフェイスに到達する前に失われたユーザーの数をカウントすることです。
搬入開始時に人数をカウントし、搬入完了後に人数を記録します。このようにして、積み込み前の人数から積み込み成功後の人数が減算されます。読み込みチャーンレートだけを知ってください。ゲームの読み込みプロセスの最適化を継続し、ゲームのユーザー読み込み速度を下げる必要があるかどうかを知ることができます。 私たちのトラフィックは最も主流の協力メディアからインポートされているためです。したがって、同時実行性は非常に高く、大まかな計算によれば、1 秒あたり約 1,000 回の同時実行に達することができるはずです。 ロード前の人数は元々ゲーム内のキャッシュプラットフォームに配置される予定でした。しかし、ゲーム バックエンドの同僚は、同時実行性が高すぎるため、リソースが不必要に浪費されるのではないかと懸念しています。メモリの解放がリアルタイムに応答しないためです。したがって、カウントされた人数を別のサーバー、つまり統計サーバーに置きます。 私が採用した解決策は次のとおりです: phpの<a href="http://www.php.cn/wiki/1311.html" target="_blank">file_get_contents<p style="text-align: left;">() と <code><a href="http://www.php.cn/wiki/1312.html" target="_blank">file_put_contents</a>
() を読み取りと書き込みに使用します。初めて読み書きするときはファイルに 1 を書き込み、2 回目に読み込むときは元のファイルに 1 を追加する、という一連の考え方で問題ありません。問題は、サーバーをシーケンシャルにできないことです。 <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()
正確に言うと、同時アクセスはシーケンシャルではありません。プレイヤー A がゲームをロードし、ファイル内の数値 100 を読み取ると (この時点では 100 であると仮定します)、プレイヤー B も 100 を読み取ります。このとき、スレッド処理プレイヤー A は 100 に 1 を加算して 101 を取得します。101 を書き込みます。ファイルに。
スレッド処理プレーヤー B も同じ結果を取得し、ファイルに 101 を書き込みます。このとき、問題が発生しますか?プレーヤー B はプレーヤー A の後にゲームをロードしたため、計算結果は 102 になるはずです。
これは同時実行によって引き起こされる問題です。このとき、 fopen()
を使用してファイルを開き、 flock()
を使用して書き込みロックを追加することを考えました。この方法がロックされていれば問題はないと誰もが考えるはずです。実際、それも間違いです。
私たちの問題は書くことではないからです。その代わり、読み取り時にデータが同期しなくなります。わかりました。現時点では、Baidu と Google についてはまったくわかりません。
🎜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 "清除失败!"; } }
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 中国語 Web サイトの他の関連記事を参照してください。