CentOS 7.9 服务器在高并发下内存泄漏严重,如何通过系统监控和调优解决内存资源浪费?

我发现一台核心业务服务器出现了 内存占用持续上涨、可用内存耗尽导致服务抖动、OOM(Out‑Of‑Memory)错误频发 的故障。系统运行的是 CentOS 7.9,业务是高并发 API 服务,每秒 QPS 峰值能达 15,000+,长期运行会出现内存泄漏现象。本文结合我们现场的硬件配置、监控数据、排查方法、调优方案、代码示例及评估表格,系统地讲述整个解决过程。


一、故障背景与硬件环境

我们故障排查的香港服务器www.a5idc.com基本参数如下:

指标 配置
操作系统 CentOS Linux 7.9.2009
内核版本 3.10.0‑1160.el7.x86_64
CPU 2 × Intel Xeon Silver 4210R (20 核/40 线程)
内存 256 GB DDR4 ECC RDIMM
磁盘 2 × 1.92 TB NVMe SSD (RAID 1)
应用 Golang RPC 服务 + Nginx 反向代理
并发模式 keepalive + 大量长连接请求
JVM (如有)无 JVM,但有 C++/Go 模块
网络 公网 1 × 10Gbps

当 QPS 持续在 10,000+ 时,系统可用内存会逐渐下降,free 变为很低,应用卡顿甚至被 oom_killer 触发宕机。


二、故障现象定位

2.1 初诊:系统内存消耗曲线分析

我们通过 free -m 周期性采样,内存消耗呈现如下趋势:

bash 复制代码
# 每 60s 记录一次内存使用
watch -n 60 free -m

日志记录:

时间 total used free buffers cached
00:00 262144 130000 50000 2000 8000
00:10 262144 180000 20000 2200 9000
00:20 262144 230000 5000 2100 8500
00:30 262144 250000 2000 2300 9200

可见 内存使用不断增加、free 线性下降,且cached/buffer 变化不大。


2.2 排查内存泄漏进程

使用 tophtop 监控时发现主要占用内存的是业务进程:

bash 复制代码
top -b -o +%MEM | head -n 15

输出:

PID USER %MEM COMMAND
31245 appuser 45.2 app_service
31246 appuser 44.8 app_service
1 root 0.3 systemd
... ... ... ...

两个 worker 进程内存占用接近 230GB


2.3 精确采样进程内存

pmap 抓取内存映射:

bash 复制代码
pmap -x 31245 | grep -E "total"

结果:

text 复制代码
total kB    238900000  235000000  390000

确认是 私有内存不断增加,极可能是业务自身在泄漏。


三、系统级监控方案建立

3.1 安装基础监控组件

我们选用以下监控栈:

  • Prometheus Node Exporter(系统监控)
  • cAdvisor(容器/进程资源监控)
  • Grafana(可视化)

Node Exporter 安装:

bash 复制代码
wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux‑amd64.tar.gz
tar -zxvf node_exporter‑*.tar.gz
cd node_exporter‑*.linux‑amd64
./node_exporter &

3.2 关键指标监控建模

指标 采集项 描述
内存 node_memory_MemAvailable_bytes 系统可用内存
进程内存 process_resident_memory_bytes 业务进程物理内存
系统负载 node_load1 1 分钟 load
Swap node_memory_SwapFree_bytes 交换区剩余
oom_killer node_oom_events_total OOM 事件计数

通过 Grafana 面板我们可以直观判断内存消耗趋势。


四、内存泄漏定位

4.1 使用 smem 纵深分析

安装 smem

bash 复制代码
yum install -y smem
smem -rt | head -n 10

重点关注 USS(独占私有内存),发现持续增长。


4.2 栈快照分析(如是 C/C++)

如果是 C/C++ 服务,我们利用 gperftools 采样:

bash 复制代码
# 启动时添加 tcmalloc 参数
LD_PRELOAD=/usr/lib/libtcmalloc_minimal.so CPUPROFILE_FREQUENCY=100
./app_service

之后通过 pprof 进行分析:

bash 复制代码
pprof --text ./app_service heap_profile

定位到内存分配热点类/函数。


4.3 如果是 Golang 进程

Golang 默认有垃圾回收统计,可以用 Go 自带 pprof:

bash 复制代码
# 在代码中引入 pprof
import _ "net/http/pprof"
go func() {
    http.ListenAndServe("localhost:6060", nil)
}()

然后抓取 heap profile:

bash 复制代码
go tool pprof http://localhost:6060/debug/pprof/heap

检查内存对象分布,定位泄漏 path。


五、调优方案与实施

5.1 系统层内存管理优化

5.1.1 调整内核 vm 参数
bash 复制代码
# 减少 swap 使用倾向
sysctl -w vm.swappiness=10
sysctl -w vm.vfs_cache_pressure=50

持久化:

bash 复制代码
cat >> /etc/sysctl.conf <<EOF
vm.swappiness=10
vm.vfs_cache_pressure=50
EOF

5.1.2 调整 OOM 保护策略
bash 复制代码
# 提高业务进程 oom_score_adj,防止优先被杀
echo 100 > /proc/31245/oom_score_adj

5.2 应用层内存泄漏修复

根据 pprof/pprof 分析的泄漏路径,我们发现某个长生命周期缓存列表未定期清理。

示例:Golang 缓存泄漏修复片段

修复前代码:

go 复制代码
var globalCache = make(map[string][]*Data)

修复后:

go 复制代码
type CacheItem struct {
    Data *Data
    Expiry time.Time
}
var globalCache = make(map[string][]*CacheItem)

func CleanUpCache() {
    ticker := time.NewTicker(10 * time.Minute)
    for range ticker.C {
        for key, items := range globalCache {
            filtered := items[:0]
            for _, item := range items {
                if item.Expiry.After(time.Now()) {
                    filtered = append(filtered, item)
                }
            }
            globalCache[key] = filtered
        }
    }
}

启动清理协程:

go 复制代码
go CleanUpCache()

5.3 业务降级与资源隔离策略

为了避免单一进程撑爆内存,我们:

  • 使用 systemd 配置资源隔离:
ini 复制代码
# /etc/systemd/system/app.service
[Service]
MemoryLimit=60G
CPUQuota=80%

重载:

bash 复制代码
systemctl daemon-reload
systemctl restart app.service

六、优化效果评估

6.1 预调优 vs 后调优对比

指标 调优前 调优后
峰值内存使用 240 GB 58 GB
系统可用内存 2 GB 40 GB+
OOM 事件 5 次/小时 0 次/小时
平均响应时间 230 ms 110 ms
99% 响应时间 1.2 s 680 ms

内存利用率明显改善,系统稳定性大幅提升。


七、监控告警规则建议

Prometheus 式告警:

yaml 复制代码
groups:
- name: memory.rules
  rules:
  - alert: HighMemoryUsage
    expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.85
    for: 3m
    labels:
      severity: critical
    annotations:
      summary: "Memory usage > 85%"
      description: "Memory usage over threshold for >3 minutes."

八、总结与经验教训

  1. 早监控、早发现:Node Exporter + Grafana 是抓取趋势的关键工具。
  2. 堆栈分析定位泄漏:pprof/tcmalloc/heap 分析能直击内存泄漏热点。
  3. 内核与应用双管齐下:仅靠系统调优无法修复业务逻辑泄漏。必须结合代码改造。
  4. 资源隔离防止级联故障:systemd cgroup 限制避免一处失控造成整机失效。
相关推荐
Nick.Q20 小时前
vim插件的管理与离线安装
linux·编辑器·vim
代码的奴隶(艾伦·耶格尔)1 天前
Nginx
java·服务器·nginx
头发还没掉光光1 天前
HTTP协议从基础到实战全解析
linux·服务器·网络·c++·网络协议·http
小白同学_C1 天前
Lab2-system calls && MIT6.1810操作系统工程【持续更新】
linux·c/c++·操作系统os
物理与数学1 天前
linux内核 struct super_block
linux·linux内核
Getgit1 天前
Linux 下查看 DNS 配置信息的常用命令详解
linux·运维·服务器·面试·maven
zhangrelay1 天前
Linux(ubuntu)如何锁定cpu频率工作在最低能耗模式下
linux·笔记·学习
_OP_CHEN1 天前
【Linux系统编程】(二十)揭秘 Linux 文件描述符:从底层原理到实战应用,一篇吃透 fd 本质!
linux·后端·操作系统·c/c++·重定向·文件描述符·linux文件
捷智算云服务1 天前
告别运维割裂!捷智算GPU维修中心重新定义“全栈式”维修新标准
运维·服务器·性能优化
chem41111 天前
玩客云 边缘AI模型 本地搭建部署 llama.cpp qwen
linux·人工智能·llama