MENU

[踩坑] PHP或许也需要主动释放内存

November 11, 2020 • Read: 1109 • 技术杂谈

事情原委是这样的:前段时间里面工作当中遇到了一个需求,需要快速的计算出流量汇总数据并且绘制一张报表,然后以邮件形式发送给指定人。

其中的流量数据并不是平常说的日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那边随后出问题。

究竟吃了多少内存?看图。
php 内存2.png

也就是说,在出一次报表期间,一个进程至少占3.5G内存...,没得玩

后来我在 $chan->push($data);前面加了一句代码,成功解决问题。

 unset($rawData);

究其原因,平常中用PHP脚本基本不关心内存分配问题,因为底层的解释器已经做得相当好了;但是在一起特殊场景下,比如我这个,加载大量数据怼到内存中进行大量计算,不复用又不及时释放,造成了内存的严重浪费!

最好看这个加了之后的效果图,这完全就是十倍的差距!

php 内存占用.png

关于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
Last Modified: November 20, 2020