You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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是多进程模型,如果两个进程同时执行脚本:

  1. 进程A读取旧的previoustx值,但还未写入新的$total。
  2. 进程B读取同一个旧值,计算并写入新的$total。
  3. 进程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
));

关键修复点

  1. API错误处理:添加CURL错误检查、JSON解析有效性验证,确保只有合法数据才参与计算。
  2. 数据有效性校验:验证容器ID、stats网络数据、字节数值的合法性,避免无效数据干扰统计。
  3. 文件锁机制:使用flock实现独占锁,确保同一时间只有一个进程读写统计文件,彻底解决多进程竞态问题。
  4. 异常值过滤:当$total小于previoustx(比如容器重启导致字节数重置)时,取差值为0,避免负数影响。
  5. 缩短超时时间:将CURL超时从0改为5秒,避免进程长时间阻塞导致后续请求堆积。

内容的提问来源于stack exchange,提问作者Lary Ciminera

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 22:47:04