ECS Fargate中.NET容器内存使用率计算偏差问题排查
问题描述
我们在AWS ECS Fargate上运行.NET 6应用的Linux Docker容器,该应用从S3流式读取1-数十GB的大对象,拆分JSON片段后加入ConcurrentQueue,同时启动多工作线程从队列取数处理。
最初因StreamReader填充队列速度远超工作线程处理速度,出现OutOfMemory错误。我们实现了基于内存的节流服务,定期检查内存占用,超过阈值时让StreamReader延迟等待,方案曾运行良好。但近期调整消费者处理逻辑后问题复现:应用内计算的内存使用率远低于ECS控制台显示值,导致内存占用飙升至100%,任务被强制终止。
当前内存使用率计算逻辑如下:
private int GetCurrentPercentMemoryUtilization() { long totalMemoryUsedBytes = default; foreach (var process in Process.GetProcesses()) totalMemoryUsedBytes += process.WorkingSet64; int precentSystemMemoryUsuage = (int)((totalMemoryUsedBytes / Math.Pow(1024, 3)) / (_runtimeEnvironment.MemoryGb) * 100); _logger.LogDebug("Total % used memory used {precentSystemMemoryUsuage}", precentSystemMemoryUsuage); return precentSystemMemoryUsuage; }
本地Windows环境调试时,该函数返回值与任务管理器数据一致,但在ECS Fargate单容器中存在明显偏差。需要调整计算逻辑,消除与控制台数据的差异。
调整方案
1. 基于容器cgroup统计内存,对齐ECS指标
ECS Fargate的内存统计是基于Docker容器的cgroup内存限制与使用数据,而非进程级统计。直接读取cgroup的内存文件能得到和控制台完全一致的数据:
- 读取
/sys/fs/cgroup/memory/memory.usage_in_bytes获取容器当前总内存使用量(字节) - 读取
/sys/fs/cgroup/memory/memory.limit_in_bytes获取容器的内存限额(即任务定义中设置的内存值)
2. 抛弃遍历所有进程的逻辑
原代码遍历容器内所有进程累加内存,在Linux容器中会包含init、sh等系统辅助进程,且.NET的Process.WorkingSet64在Linux上的统计范围与ECS的cgroup统计存在差异,导致数据不准。直接读取cgroup数据更高效且准确。
3. 修正单位转换的精度问题
原代码用Math.Pow(1024,3)做GB转换,会引入浮点数精度丢失,改用整数运算1024L * 1024 * 1024避免该问题。
调整后的代码示例
private int GetCurrentPercentMemoryUtilization() { long memoryUsedBytes; long memoryLimitBytes; try { // 读取cgroup内存使用量 memoryUsedBytes = long.Parse(File.ReadAllText("/sys/fs/cgroup/memory/memory.usage_in_bytes").Trim()); // 读取cgroup内存限额 memoryLimitBytes = long.Parse(File.ReadAllText("/sys/fs/cgroup/memory/memory.limit_in_bytes").Trim()); } catch (Exception ex) { // 降级逻辑:无法读取cgroup时,使用当前进程内存和配置的实例内存 _logger.LogWarning(ex, "Failed to read cgroup memory stats, falling back to process-level stats"); memoryUsedBytes = Process.GetCurrentProcess().WorkingSet64; memoryLimitBytes = _runtimeEnvironment.MemoryGb * 1024L * 1024 * 1024; } // 处理Fargate中罕见的无硬限制情况(通常不会出现) if (memoryLimitBytes == long.MaxValue) { memoryLimitBytes = _runtimeEnvironment.MemoryGb * 1024L * 1024 * 1024; } int percentMemoryUsage = (int)(((double)memoryUsedBytes / memoryLimitBytes) * 100); _logger.LogDebug("Total memory usage percent: {percentMemoryUsage}", percentMemoryUsage); return percentMemoryUsage; }
注意事项
- Linux Docker容器默认拥有读取
/sys/fs/cgroup/memory/目录的权限,无需额外配置权限策略。 - Fargate任务的内存限额与任务定义中设置的内存值完全一致,读取cgroup的
memory.limit_in_bytes比手动维护的_runtimeEnvironment.MemoryGb更准确。 - 若出现cgroup文件读取失败的情况,降级到当前进程内存统计可保证节流逻辑继续运行。
内容的提问来源于stack exchange,提问作者smk081
相关产品推荐
相关产品推荐

