이번에는 PHP 파일 읽기 및 쓰기의 높은 동시 처리 작업 단계를 분석하고, PHP 파일 읽기 및 쓰기의 높은 동시 처리를 위한 주의 사항 에 대해 설명하겠습니다. 다음은 실제 사례입니다. 보세요.
최근 회사의 게임 개발에서는 게임 로딩의 이탈률을 알아야 합니다. 왜냐하면 우리는 웹 게임을 만들고 있기 때문입니다. 웹 게임을 해본 사람이라면 게임에 들어가기 전에 일부 리소스를 로드해야 한다는 것을 알고 있을 것입니다. 마지막으로 캐릭터 생성을 위한 게임 인터페이스에 접근할 수 있습니다. 우리가 필요로 하는 것 중 하나는 로딩 과정에서 캐릭터 생성 인터페이스에 도달하기 전에 사라진 사용자 수를 세는 것입니다. 로딩 시작시 인원수를 세고, 로딩이 완료된 후 인원수를 기록합니다. 이런 식으로 로딩 전 인원수에서 로딩 성공 후 인원수를 빼면 됩니다. 로딩 이탈률만 알아두세요. 게임이 로딩 프로세스를 계속 최적화하고 게임의 사용자 로딩 속도를 줄여야 하는지 여부를 알 수 있습니다. 우리의 트래픽은 가장 주류 협력 미디어에서 수입되기 때문입니다. 따라서 동시성이 매우 높은 것으로 대략적인 계산에 따르면 초당 약 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<code><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()
을 사용하여 쓰기 잠금을 추가하려고 생각했습니다. 이 방법이 잠겨 있으면 문제가 발생하지 않을 것이라고 누구나 확실히 생각할 것입니다. 사실 그것은 또한 잘못된 것입니다.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!