事情原委是这样的:前段时间里面工作当中遇到了一个需求,需要快速的计算出流量汇总数据并且绘制一张报表,然后以邮件形式发送给指定人。
其中的流量数据并不是平常说的日IP量,指的是一个IP的上下行流量,一次计算时间跨度就是一个月,所以计算量相当大,而计算数据是来源另外一台mysql日志服务器上面;因此我顺便写了一个脚本定时一小时一次同步一次,在本地生成一个个以json格式保存的缓存文件;之所以这样做是怕这边在大量计算的时候并发处理,怕把日志服务器搞崩,那事情就大了。
所以在计算数据的时候需要从大量缓存文件中加载数据,读取文件,每次一旦开始计算,便是32个协程共同处理(swoole,16H,32G内存)。
核心代码大概如下:
go(function () use ($tasks, $chan, $ipList, $wg) {
$db = getPdo();
$where = WHERE;
$cid = Coroutine::getCid();
foreach ($tasks as $table) {
$cache = PATH_DATA . '/cache/' . mb_substr($table, -10) . '_minute.json';
if (file_exists($cache)) {
$rawData = json_decode(file_get_contents($cache), true);
toLog("Co:{$cid} 加载缓存:{$table}");
} else {
$rawData = [];
$rows = $db->query("select * from {$table} where {$where}");
if (empty($rows)) {
continue;
}
// 颗粒度为分钟,计算每个IP的总上下行流量
$rows = $rows->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$ip = $row['s_ip'];
$minute = date('YmdHi', $row['time']);
$rawData[$minute][$ip] = [
'up' => (int)$rawData[$minute][$ip]['up'] + $row['up'],
'down' => (int)$rawData[$minute][$ip]['down'] + $row['down'],
];
}
toLog("Co:{$cid} 生成缓存:{$table}");
file_put_contents($cache, json_encode($rawData));
}
// IP组总流量计算
$data = [];
foreach ($rawData as $minute => $list) {
foreach ($list as $ip => $item) {
$data[$minute]['up'] += isset($ipList[$ip]) ? (int)$item['up'] : 0;
$data[$minute]['down'] += isset($ipList[$ip]) ? (int)$item['down'] : 0;
}
}
$chan->push($data);
}
$wg->done();
});
就这样用了一个多月,平常也没什么问题,但最近一段时间查流量,短时间内需要出很多报表;结果有时MYSQL自动搞崩了???
其实现在这个时候MYSQL里面已经有500多G的数据了,平时也比较吃内存,后来查了好久查出来,就是因为这个脚本计算吃了非常多内存,Mysql那边随后出问题。
究竟吃了多少内存?看图。
也就是说,在出一次报表期间,一个进程至少占3.5G内存...,没得玩
后来我在 $chan->push($data);
前面加了一句代码,成功解决问题。
unset($rawData);
究其原因,平常中用PHP脚本基本不关心内存分配问题,因为底层的解释器已经做得相当好了;但是在一起特殊场景下,比如我这个,加载大量数据怼到内存中进行大量计算,不复用又不及时释放,造成了内存的严重浪费!
最好看这个加了之后的效果图,这完全就是十倍的差距!
关于PHP内存管理的一些文章:
https://mp.weixin.qq.com/s/XIuto7yzBwr7cCiws_kFUw
https://segmentfault.com/a/1190000024464967
https://v2ex.com/t/630530
https://m.xp.cn/b.php/66637.html