PHP计算Docker容器总带宽时出现异常超大值问题排查
问题分析与解决方案
你的问题大概率由两个核心原因导致:API请求失败时的无处理逻辑,以及多进程环境下的文件读写竞态条件,以下是具体分析和修复方案:
核心问题1:API请求失败导致统计值重置
你的GetData函数没有任何错误处理:如果Docker API请求超时、失败,或者返回非JSON格式数据,json_decode会返回null或false。此时:
- 当获取容器列表的
GetData('json')失败时,$data为空,循环不执行,$total保持为0并写入文件。 - 下一次正常调用时,计算出的正常
$total减去0,再除以10秒,就会得到异常超大的带宽值(正好匹配你遇到的203262380683 B/s这类数值)。
核心问题2:PHP-FPM多进程的竞态条件
PHP-FPM是多进程模型,如果两个进程同时执行脚本:
- 进程A读取旧的
previoustx值,但还未写入新的$total。 - 进程B读取同一个旧值,计算并写入新的
$total。 - 进程A随后写入自己计算的
$total,可能覆盖掉正确的数值,导致下一次计算出现偏差。
修复后的代码
function GetData($url) { $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => "http://host.docker.internal:2375/v1.41/containers/$url", CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 5, // 缩短超时,避免长时间阻塞 CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'GET', )); $response = curl_exec($curl); // 添加CURL错误检查 if(curl_errno($curl)) { curl_close($curl); return false; } $res = json_decode($response, true); curl_close($curl); // 验证JSON解析结果是否有效 if(!is_array($res)) { return false; } return $res; } // fetch all container $data = GetData('json'); $total = false; if(is_array($data)) { $total = 0; foreach ($data as $container) { // 验证容器ID存在 if(empty($container["Id"])) continue; $id = $container["Id"]; $url = "$id/stats?one-shot=true&stream=false"; // fetch container stats $stat = GetData($url); // 验证stats数据有效 if(!is_array($stat) || empty($stat['networks'])) continue; $networks = $stat['networks']; // sum all i/o bytes foreach ($networks as $nt) { // 验证字节数值有效(必须是整数) if(!isset($nt["rx_bytes"], $nt["tx_bytes"]) || !is_int($nt["rx_bytes"]) || !is_int($nt["tx_bytes"])) continue; $total += $nt["rx_bytes"] + $nt["tx_bytes"]; } } } // get previous reading with file lock to avoid race condition $filename = getcwd() . "/previous.txt"; $bandwidth = 0; $fileHandle = fopen($filename, 'c+'); if($fileHandle) { // 获取独占锁,避免多进程同时读写 if(flock($fileHandle, LOCK_EX)) { if(filesize($filename) > 0) { $previoustx = intval(fread($fileHandle, filesize($filename))); $previoustime = filemtime($filename); $newtime = time(); $elapsed = $newtime - $previoustime; if ($elapsed > 0 && $total !== false) { // 确保total大于等于previoustx,避免负数(容器重启等情况) $diff = max(0, $total - $previoustx); $bandwidth = round($diff / $elapsed); } } // 只有当total有效时才写入文件 if($total !== false) { ftruncate($fileHandle, 0); rewind($fileHandle); fwrite($fileHandle, $total); } flock($fileHandle, LOCK_UN); } fclose($fileHandle); } echo json_encode(array( "version" =>"v1.41", "bandwidth" => $bandwidth ));
关键修复点
- API错误处理:添加CURL错误检查、JSON解析有效性验证,确保只有合法数据才参与计算。
- 数据有效性校验:验证容器ID、stats网络数据、字节数值的合法性,避免无效数据干扰统计。
- 文件锁机制:使用
flock实现独占锁,确保同一时间只有一个进程读写统计文件,彻底解决多进程竞态问题。 - 异常值过滤:当
$total小于previoustx(比如容器重启导致字节数重置)时,取差值为0,避免负数影响。 - 缩短超时时间:将CURL超时从0改为5秒,避免进程长时间阻塞导致后续请求堆积。
内容的提问来源于stack exchange,提问作者Lary Ciminera
相关产品推荐
相关产品推荐

