多进程监控方案:从命令行到开源系统,覆盖Linux与Windows
很多跑服务的机器上十几个进程各管一摊nginx管入口MySQL存数据Java后端支撑接口Redis做缓存文件同步、日志采集、定时任务也各有一个进程。最难受的不是某一个服务挂了而是一挂挂好几个你还最后一个知道。今天这篇文章就讲清楚一套监控多个进程存活、CPU和内存占用的完整方案从命令行手工查到脚本实现再到接入开源监控系统覆盖 Linux 和 Windows 两种环境。适合既要管几台小服务器、又不想一上来就上 K8s 的开发者也适合想把手头服务梳理一遍的运维新人。1. 监控前先想清楚你到底要监控进程的哪一面很多人一上来就搜“怎么监控进程CPU”然后抄个脚本跑起来结果第二天收到一堆莫名其妙的告警。原因很简单需求没拆清楚。进程监控至少有“存活”“资源消耗”“业务可用性”三个维度你要的通常不止一个。1.1 存活判断的三个层次第一层是操作系统层面进程 PID 还在。PID 在就代表进程活着吗不一定。一个进程可能早就卡死但内核角度看它还在没退出。第二层是进程名或命令行匹配能确认“我想要的那个进程”是否在系统列表里。但一个服务进程还在可能线程池已经满了请求全部超时。第三层是业务可用性比如端口能连上、HTTP 接口能正常返回、数据库连接池没占满。设计监控之前先决定做到哪一层。如果只是担心“进程不见了”PID/进程名判断就够了。如果目标是“服务别挂”那必须加上端口探测或 HTTP 健康检查。我见过一个 Java 应用进程活得好好的内存也没爆但因为内部线程全部卡在等待锁上外部请求全超时。如果没有业务探活这种故障根本发现不了。对这篇标题来说我们核心讲 PID/进程名级别的存活检测加上 CPU、内存资源监控。但我会在思考清单里提醒你业务探活是另一个必须单独补的环节。1.2 CPU 和内存要用哪套口径CPU 使用率有两套完全不同的口径拿到手的数据含义差很多。一套是“进程从启动到现在平均占用了百分之多少的 CPU”。Linux 下ps -o %cpu就是这种累计平均口径。一个进程刚启动的前几分钟它可能满负荷跑但平均下来显示 20%因为你把启动之后的所有空闲时间也一起平均了。这种值做趋势报警基本没有意义。另一套是“最近一段时间的瞬时使用率”比如过去 1 秒、5 秒、60 秒。top默认显示的就是这种相对值但top批处理模式有坑我后面会专门讲。pidstat这种工具可以直接采样而 Prometheus 里用rate()函数算出来的也是趋势值。监控应该用后一套至少也要是最近一个时间窗口的采样值。内存口径更多。最常看到的是 RSSResident Set Size进程实际驻留在物理内存里的字节数。RSS 包含多个进程之间共享的共享库比如 nginx 的多个 worker 都加载同一个 libcRSS 会把它算到每个进程头上。你要做“这个进程是不是有内存泄漏”的趋势监控RSS 够用但你要是做“这个容器所有进程一共占了多少内存”RSS 加总很可能是高估的。更准确的是 PSSProportional Set Size按共享比例分摊但读起来成本高一点后续在踩坑章节里细讲。1.3 被监控对象的身份标识最容易翻车的就是进程匹配。很多人用进程名去 grep结果系统里有七八个同名进程你根本不知道哪个是你要的。比如 Java 应用进程名永远都是java区别只体现在命令行参数里Python 写的服务进程名可能是python你同样分不清哪个是哪个。所以在一开始就要给每个监控对象建立一份“身份档案”包含三样东西唯一匹配特征通常是完整命令行里某段独有的参数比如-Dspring.profiles.activeprod、config.py --role worker。业务名称给你自己看的比如“订单服务”“支付网关”。资源阈值CPU 告警阈值、内存告警阈值、判定连续超限多少次才告警。这份清单是后续任何脚本、任何监控系统配置的根基。我强烈建议用文本文件或 YAML 管起来不要散落在脚本里。2. 用系统自带命令手工摸底ps、top、wmic 与 PowerShell很多场景不需要一上来就写程序。先把系统自带的工具用熟练一方面能验证真实数据长什么样另一方面也能帮你快速排查“为什么脚本报错了”。我平时排查问题第一件事永远是手动跑命令看原始输出。2.1 Linux 下用 ps 和 top 初筛指标Linux 里最基础的是ps。我的习惯命令是ps -eo pid,ppid,comm,%cpu,%mem,rss,vsz,etime,args | grep -E nginx|java | grep -v grep解释一下字段pid是进程号ppid是父进程号comm是进程名%cpu注意是累计平均值%mem是驻留内存占物理内存的比例rss是驻留内存大小KBvsz是虚拟内存大小etime是运行时长args是完整命令行。用grep过滤时容易把自己也匹配进去很多人会看到输出里多了一个grep --colorauto -E nginx的伪进程。两个解决办法一是加grep -v grep二是用pgrep -f 关键字这种更正规的方式。pgrep -f会把命令行完全做子串匹配但同样会匹配到你不想看的进程所以带完整特征参数再过滤。top在交互模式看瞬时值很方便可脚本里取 CPU 值有个大坑。你可能会想用top -b -n 1输出一次但这个模式下第三行的%Cpu(s)是系统从开机到现在的累计平均而下面进程列表的%CPU其实是“上下两次刷新之间”的值。如果你只跑一次top -b -n 1它没有“上一次刷新”所以拿到的进程 CPU 会偏低。要拿到准的得用top -b -n 2 -d 1然后取第二次输出的结果或者干脆用pidstat -p PID 1 1。这个细节坑过很多人。2.2 Windows 平台怎么拿进程状态和资源占用Windows 的命令行工具没那么直接。先看最传统的tasklisttasklist /FI IMAGENAME eq java.exe它能列出进程名、PID、会话、内存工作集但拿不到 CPU 使用率。再看wmicwmic process where namejava.exe get ProcessId,WorkingSetSize,CommandLine这个能拿到命令行和内存字节数WorkingSetSize 单位是字节同样没有 CPU 使用率只有内核模式和用户模式时间累计值。Windows 上想得到百分比形式的 CPU 占用更靠谱的是用 PowerShell 配合性能计数器Get-Process | Select-Object Name,Id,CPU,WorkingSet64 Get-Counter \Process(java)\% Processor TimeGet-Process里的CPU属性是进程累计消耗的 CPU 时间秒不是百分比。而Get-Counter的性能计数器% Processor Time需要转成“某个进程占所有 CPU 核心的比例”在多大核机器上含义完全不一样。还有一个更现代的方案用Get-CimInstance Win32_Process替代已移除的 wmic可以拿命令行和进程启动时间。Windows 下的监控脚本建议直接用 PowerShell 写比 cmd 好处理得多。2.3 从 /proc 里直接读数的利与弊Linux 下想看最底层的真实数据绕不开/proc文件系统。每个运行中进程对应一个/proc/pid/目录里面有几个关键文件stat进程状态、CPU 时间、启动时间、父进程号等。status人类可读的 Name、State、VmRSS、VmSwap 等。smaps_rollup按内存映射汇总包含 RSS、PSS 等更细的数据。fd/文件描述符目录能看出进程打开了多少文件。比如我想手动算一个进程最近 5 秒的 CPU 使用率思路是读两次/proc/pid/stat里的第 14 和第 15 字段用户态 CPU 时间和内核态 CPU 时间两次相减再除以墙钟时间差再除以 CPU 核数做归一化。示例命令cat /proc/1234/stat | awk {print $14$15} sleep 5 cat /proc/1234/stat | awk {print $14$15}两次相减的差就是 5 秒内消耗的 CPU 时间单位是时钟节拍通常除以getconf CLK_TCK得到秒。这个差值除以采样间隔再除以 CPU 核数就得到使用率。这种方案的优点是精准、不受top/ps版本影响缺点是脚本解析逻辑要写对字段序号一旦内核字段变化就会踩坑代码可读性也差。日常监控我不建议直接用/proc除非你要做性能分析工具。2.4 手工排查阶段怎么确认“进程是否还活着”看完进程指标后还有一个经典问题进程 PID 在但到底是不是在正常工作手工排查时我一般会三连看。第一看端口和连接ss -ltnp | grep PID看进程监听端口是否还在如果进程还在但端口没了多半是 bind 失败或配置异常。第二看文件句柄变化ls -l /proc/PID/fd | wc -l连续执行两次如果数量基本不动且这个进程本来是个网络服务那它可能已经假死或者连接泄漏卡住了。第三看日志时间戳进程输出文件或系统日志如果好几分钟没新内容就算ps显示S状态也要提高警惕。这套“手工三板斧”在监控脚本报警时特别好用因为能直接确认是误报还是真故障。3. 写一个不依赖第三方库的监控脚本Linux shell/Python psutil 双实现手工命令摸清楚之后下一步就是写自动化巡检脚本。如果你的环境不允许安装额外的 agent或者你只是想快速跑一个内网小工具用 shell 或 Python 就够了。我分别讲一条路径但主要推荐 Python psutil 方案。3.1 先确定采集逻辑与阈值写脚本前先列一张清单不要边写边想。我的套路是确定五件事监控频率默认 30 秒一次追求更细粒度的 CPU 趋势可以压到 10 秒但频率越高日志量越大。检查哪些进程建立“服务名 / 匹配关键字 / CPU 阈值 / 内存阈值”的表。存活判断方式进程匹配不到就告警匹配到再看资源。CPU 判定规则持续几个周期超阈值才算告警避免偶发尖峰误报。内存判定规则超过 RSS 阈值或连续几次都大于某个数说明不是瞬时波动。阈值要写成配置不要写死在逻辑里。比如 Java 服务因为 JVM 预留堆内存可能启动就占 2GB而 nginx 才占几十 MB阈值完全没有可比性。3.2 Linux shell 脚本ps awk 实现进程状态与 CPU/内存采集先给一个简单但能用的 shell 版本。它按“服务名|匹配参数|CPU告警阈值%|内存告警阈值MB”来配置用pgrep -f找进程用ps读取资源#!/bin/bash # proc_mon.sh - 一次性巡检脚本建议配合 cron 或 systemd timer 执行 MONITOR_LIST/etc/proc_mon.conf check_proc() { local name$1 local pattern$2 local cpu_thr$3 local mem_thr$4 local pids pids$(pgrep -f $pattern) if [ -z $pids ]; then echo [ALERT] $name 进程不存在 return fi for pid in $pids; do local info info$(ps -p $pid -o %cpu,%mem,rss,etime --no-header) if [ -z $info ]; then continue fi local cpu rss_mb cpu$(echo $info | awk {print $1}) rss_mb$(echo $info | awk {print $3/1024}) if awk BEGIN{exit !($cpu $cpu_thr)}; then echo [ALERT] $name PID$pid CPU$cpu% ${cpu_thr}% fi if awk BEGIN{exit !($rss_mb $mem_thr)}; then echo [ALERT] $name PID$pid RSS${rss_mb}MB ${mem_thr}MB fi done } while IFS| read -r name pattern cpu_thr mem_thr; do [ -z $name ] continue check_proc $name $pattern $cpu_thr $mem_thr done $MONITOR_LIST这个脚本有两个重要提醒。第一它对进程是否存活用pgrep -f判断你配置的匹配关键字一定要足够唯一。我以前用nginx做关键字结果把nginx_exporter、nginx-proxy全部匹配进来了那叫一个酸爽。第二ps -o %cpu是累计平均值不是瞬时值所以这个版本的 CPU 检测只能作为粗略参考。如果你要瞬时 CPU把ps换成pidstatpidstat -p $pid 1 1 | tail -1 | awk {print $8}pidstat需要安装sysstat如果你的系统限制装包那就用下一次的/proc差值。3.3 Python psutil跨平台监控的推荐选择如果条件允许我强烈建议用 Python 加psutil因为跨平台、API 设计得舒服省去很多 shell 解析的坑。先安装pip install psutil然后是一个通用巡检脚本的核心逻辑import time import psutil MONITORS [ {name: nginx, pattern: nginx: master, cpu_max: 80, rss_max_mb: 512}, {name: order-service, pattern: java -jar order-service.jar, cpu_max: 85, rss_max_mb: 2048}, ] def find_pids(pattern: str) - list[int]: result [] for proc in psutil.process_iter([pid, cmdline]): try: cmdline .join(proc.info[cmdline] or []) if pattern in cmdline: result.append(proc.info[pid]) except (psutil.NoSuchProcess, psutil.AccessDenied): continue return result def sample_cpu(pid: int, interval: float 1.0) - float: p psutil.Process(pid) p.cpu_percent(None) time.sleep(interval) return p.cpu_percent(None) def check_process(entry: dict) - None: pids find_pids(entry[pattern]) if not pids: print(f[ALERT] {entry[name]} 进程不存在) return total_cpu 0.0 total_rss_mb 0.0 for pid in pids: try: proc psutil.Process(pid) cpu sample_cpu(pid, interval1) rss_mb proc.memory_info().rss / 1024 / 1024 total_cpu cpu total_rss_mb rss_mb if rss_mb entry[rss_max_mb]: print(f[ALERT] {entry[name]} PID{pid} RSS{rss_mb:.1f}MB) except psutil.NoSuchProcess: print(f[ALERT] {entry[name]} PID{pid} 已不存在) if total_cpu entry[cpu_max]: print(f[ALERT] {entry[name]} 总CPU{total_cpu:.1f}%)这段代码有几个细节值得展开。第一psutil.process_iter([pid, cmdline])用cmdline做匹配等于和pgrep -f一个思路所以匹配串还是要带唯一参数。第二proc.cpu_percent(None)第一次调用会返回 0因为它需要和上一次调用做差。所以sample_cpu里先调用一次预热然后 sleep 1 秒再取第二次结果。第三如果一台机器上同名服务的多进程加起来 CPU 比较高但单个进程都很低那你要决定是按单进程告警还是按服务总和告警。上面代码里我加总了所有匹配到的进程更适合“一个服务开了多个 worker”的业务形态。第四cpu_percent返回的值在多核机器上可能超过 100%比如 8 核机器上你把 4 个核吃满这个进程的 CPU 是 400%。如果你用默认的 80 当阈值一个正常的、故意利用多核的进程会一直告警。所以阈值的设定必须结合“这个服务应该用几核”来判断经验做法是先观察一个礼拜正常值再设阈值。3.4 数据落盘与报警触发的简单实现巡检脚本光把结果打印到控制台没有意义。我会做一个很轻量的处理把每次巡检结果以 JSON 行格式写到本地日志同时把告警单独推到 IM 机器人。日志落盘可以用标准库import json from datetime import datetime def log_check(entry, pids, cpu, rss_mb, alerts): record { time: datetime.now().isoformat(), service: entry[name], pids: pids, cpu: cpu, rss_mb: rss_mb, alerts: alerts, } with open(/var/log/proc_mon.log, a) as f: f.write(json.dumps(record, ensure_asciiFalse) \n)报警推送更简单企业微信/钉钉机器人都只是往一个 webhook 地址 POST JSON。我习惯用urllib避免多装依赖import urllib.request def send_webhook(url: str, content: str): data json.dumps({msgtype: text, text: {content: content}}).encode() req urllib.request.Request(url, datadata, headers{Content-Type: application/json}) urllib.request.urlopen(req, timeout5)要注意 webhook 地址本身就是告警通道不能写死在代码里更不能提交到仓库建议放到环境变量或单独配置文件里。3.5 定时执行的正确姿势cron 与 systemd timer脚本写好后如果你直接用nohup python3 proc_mon.py 丢后台那么这个进程一旦自身挂掉监控就没人管了。生产环境里不要让任何监控程序裸奔一定要交给进程守护来拉起。最简单的场景巡检频率是分钟级直接写 cron*/1 * * * * /usr/bin/python3 /opt/proc_mon/proc_mon.py /var/log/proc_mon_cron.log 21但这种模式只适合“每次执行完就退出”的一次性巡检脚本。如果你的脚本是while True sleep常驻模式最好用 systemd unit[Unit] DescriptionProcess monitor script Afternetwork.target [Service] ExecStart/usr/bin/python3 /opt/proc_mon/proc_mon.py Restartalways RestartSec5 Userroot EnvironmentFile/etc/proc_mon.env [Install] WantedBymulti-user.target然后用systemctl enable --now proc_mon启起来。这样监控脚本本身有 systemd 看着挂掉 5 秒内自动重启比裸后台稳太多。我见过不少人辛辛苦苦写好监控结果自己没被监控最后反而是监控进程默默退出了才导致漏报。4. 把监控做成长期服务开源监控系统如何管好一堆进程脚本方案适合个人小项目和出问题的第一现场。但如果你有几十台机器、要长期保留历史数据、还要多人协作看板那就应该上成熟监控体系。这一节讲 Prometheus 生态里怎么监控进程级指标。4.1 Prometheus node_exporter 怎么暴露进程指标先搭一个最小体系Prometheus 负责抓取和存储指标node_exporter 负责暴露主机指标Grafana 负责画图Alertmanager 负责报警。node_exporter 本身有collector.processes启用方式是在启动参数里加node_exporter --collector.processes它暴露的指标主要是node_processes_num、node_processes_threads、node_processes_state这些按进程状态或用户名聚合的数据。它能告诉你“这台机器上有多少个进程处于 sleep 状态”但不能按你的业务规则精确匹配某一个 Java 服务。要按自定义命令行分组监控得上专门的进程 exporter。4.2 用 process_exporter 按自定义规则匹配进程开源社区里有一个很常见的项目叫process-exporter功能就是按你写的规则把进程分组然后暴露存活数、CPU 时间、内存等指标。它对应的 exporter 进程名通常是process_exporter。安装好后核心是写一个 YAML 规则文件process_names: - name: nginx cmdline: - nginx: master - name: order-service cmdline: - java -jar order-service.jar然后启动process_exporter -config.path/etc/process_exporter.yaml -procfs/proc -web.listen-address:9256Prometheus 抓到这个 exporter 之后你会看到几个关键指标namedprocess_namegroup_num_procs这个分组当前有几个进程长时间为 0 说明挂了。namedprocess_namegroup_cpu_seconds_total累计 CPU 时间算使用率要用rate。namedprocess_namegroup_memory_bytes分组内各进程的内存字节数。CPU 使用率用 PromQL 写是这样rate(namedprocess_namegroup_cpu_seconds_total{groupnamenginx}[2m]) * 100这个值是“每秒消耗了多少 CPU 秒”乘以 100含义是占用了多少个核心。如果想让它在 8 核机器上显示为“占总 CPU 的百分比”再除以机器核数rate(namedprocess_namegroup_cpu_seconds_total{groupnamenginx}[2m]) * 100 / count(node_cpu_seconds_total{modeidle})不过多数看板习惯直接用“占核心数”来表示 Java 这种多线程进程反而更直观。4.3 Grafana 看板与告警规则配置Prometheus 采集的指标可以写到 Grafana 做可视化。社区有很多现成的 process-exporter 看板导入 JSON 时注意挑指标命中的版本。告警规则同样写 Prometheusgroups: - name: process_alerts rules: - alert: ProcessDown expr: namedprocess_namegroup_num_procs{groupnameorder-service} 0 for: 1m labels: severity: critical annotations: summary: order-service 进程不存在 - alert: ProcessHighCPU expr: rate(namedprocess_namegroup_cpu_seconds_total{groupnameorder-service}[5m]) * 100 6 for: 5m labels: severity: warning annotations: summary: order-service 持续高CPUfor: 5m的意思是“指标连续违反 5 分钟才告警”这个比脚本里自己写连续计数方便可以滤掉短时抖动。Alertmanager 再把告警按路由发送到不同渠道。整个体系的好处是数据落库、历史可查、多人协作看同一块屏不用再折腾日志文件。4.4 monit 和 supervisor 在进程守护里的定位差异有人会问既然 Prometheus 已经能监控为什么还有人用 supervisor 和 monit因为它们解决的不是同一个问题。supervisor 是一个进程守护/管理器它启动一个子进程后能盯着它发现退出就按配置重启。但它只对自己 fork 出来的子进程有效你系统自带的 nginx、MySQL 不是它启动的它默认管不着。monit 则是独立守护程序既能监测进程 PID 和端口又能在异常时执行外部命令拉起服务。很多人把 monit 和 supervisor 二选一其实可以用在一起supervisor 管业务进程启停monit 管系统级进程和文件系统、负载等整体健康。这里要提醒一句守护只能解决“进程退出”这种故障不能解决“进程 CPU 跑满但还活着”或者“内存悄悄泄漏”这种更隐蔽的问题。所以别以为上了 supervisor 就一劳永逸CPU/内存监控仍然必要。5. 实战踩坑记录进程监控最容易翻车的五个细节这部分全是我自己踩过或者帮别人排查时见过的坑。内容很碎但每一条都可能导致监控误报漏报值得逐条看。5.1 进程名匹配把同名进程一网打尽的悲剧用pgrep -f nginx会匹配到nginx: master process、nginx: worker process也可能匹配到某个监控脚本的命令行里恰好含nginx字符串。我曾见过有人写pgrep -f python去监控某个 Python 爬虫服务结果把这个巡检脚本自己也匹配进去了因为命令行里就带着python3 /opt/proc_mon.py那叫一个混乱。正确做法是“匹配唯一命令行片段”比如pgrep -f python3 /data/spider/main.py --crawlershop或者用 Java 服务的-Dapp.namexxx特征参数。实在没啥可用的就手动给服务加一个环境变量或启动参数用那个参数当身份标识。5.2 CPU 使用率一会儿 100 一会儿 0采样方式造成的错觉我自己就犯过这个错写脚本用proc.cpu_percent()第一次循环全是 0后面又是 100 又回到 0以为服务有问题。后来才发现psutil的cpu_percent要前后两次调用之间做差第一次没有基准值只能返回 0。第二个坑是采样间隔太短1 秒以内的 CPU 波动受调度噪声影响非常大。解决方式我已经在第 3.3 节写过先预热调用一次再sleep一个稳定间隔建议至少 2 秒再取结果。还要注意多线程进程的 CPU 值可以超过 100%阈值别拍脑袋设成 80要看这个服务实际能占用多少核。我现在给 Java 服务设 CPU 告警阈值会先观察一个礼拜取 P95 值再定阈值比一开始就用 100% 靠谱得多。5.3 内存占用看着疯涨RSS 和 PSS 该选谁RSS 是最常见的指标但它有可能误导你。举个例子一台机器跑着两个 Python 进程它们都是import了同一个大型本地模块这个模块的共享内存会被 RSS 计算两次。你看到每个进程占用 500MB以为一共 1GB实际上物理内存只占 600MB。更常见的坑是“内存告警了但服务没异常”。Java 进程的堆内存、JIT 编译后的代码缓存、堆外 buffer 都会计入 RSS一个稳定运行的 JVM 刚启动时内存可能快速上涨之后进入平台期。你要做的是区分“快速上涨后稳定”和“持续线性上涨”后者才是内存泄漏的样子。看趋势的话RSS 完全够用如果想要准确值读/proc/pid/smaps_rollup里的 PSS或用 psutil 的memory_full_info()但这个方法在部分内核上很耗 CPU尤其是进程数量多的机器别高频跑。5.4 PID 复用监控存活的另一个隐藏坑监控脚本如果缓存了 PID第二次检查时直接psutil.Process(pid)很可能碰上这个进程已经退出、PID 被新进程复用的场景。新进程当然活着但你监控的结果会把“旧进程已经死了”误判成“服务活着”从而漏掉真正的故障。怎么识别最简单的是对比进程启动时间。/proc/pid/stat的第 22 字段是进程启动时间单位是系统启动后的时钟节拍数psutil里有现成的proc.create_time()。每次检查时取到当前 PID 的启动时间和上一次记录的启动时间做对比如果对不上说明进程已经被替换了。另一个方式是监控脚本别缓存 PID每次都重新按命令行匹配让系统告诉我们“现在到底有没有这个特征进程”。这种方式天然避开 PID 复用问题所以我更推荐每次全量扫描。5.5 Windows 服务的进程权限问题在 Windows 上写同类型的监控脚本最容易遇到的就是权限和隔离问题。普通权限下某些系统服务或用户态服务进程的信息读不全Get-Process可能看不到命令行Process Explorer能看但你脚本拿不到。我的经验是要么让监控任务以管理员身份运行要么注册成 Windows 服务在服务账户下跑别用普通用户权限去尝试读别人家的进程。另外老掉牙的wmic在 Windows 11 里默认不再提供服务新的环境直接用 PowerShell 的Get-CimInstance Win32_Process或Get-Process就好。Windows 上没有/proc这种直读机制想拿到稳定的瞬时 CPU 值还是老老实实用性能计数器别省事。6. 报警渠道与值班策略监控的最后一步是让人知道脚本能探测、存储、展示还不算完最后一步是把故障“推到人面前”。如果告警没发出去或者被忽略前面全白做。6.1 报警渠道直接选可用性最高的邮件钉钉/企业微信机器人邮件不是不好但很多人根本不会实时看邮件。我自己是“IM 机器人为主邮件为辅”。企业微信/钉钉的群机器人本质上就是 HTTP webhook脚本里urllib或curl都能发。内容要带这几个字段服务名、主机 IP、告警类型进程不存在/CPU 超限/内存超限、当前值、持续时长。一个示例告警消息[告警] order-service 进程不存在 主机192.168.10.12 检查时间2025-01-20 10:32:11 详情没有匹配到 java -jar order-service.jar 的进程这种消息看起来简单但在处理故障时能直接定位是哪个服务、哪台机器比只发一行“检测到异常”有用得多。如果公司有条件可以再接一个简单的电话升级但千万别一开始就用电话轰炸否则几天后大家都会把监控当成狼来了反而没人看。6.2 静默规则与升级策略告警噪音是监控系统最大的威胁之一。我见过某团队配了一个“CPU 1%”的告警规则结果一天收几千条最后所有人直接静音整个告警群真故障来的时候反而没人响应。正确做法有三点第一设置重复告警的静默时间比如同一个服务同一类告警 15 分钟内不重复发第二设置维护窗口凌晨做发布变更时临时静默避免误报第三分级升级比如进程不存在属于严重告警CPU 高属于普通告警普通告警 15 分钟没恢复再升级为严重。脚本里可以先打标记Prometheus 的 Alertmanager 里则有现成的抑制和分组机制这也是我推荐上 Prometheus 的原因之一。6.3 监控日志怎么留证据告警消息只是一个快照真正排查时需要的是历史趋势。我的建议是每次巡检无论是否告警都留一条 JSON 日志{time:2025-01-20T10:31:00,service:order-service,pids:[12345],cpu:12.3,rss_mb:1876,alerts:[]}这样当服务在 10:40 出问题时往前翻 20 分钟就能看到内存是逐步涨上来的还是突然跳变的CPU 是从什么时候开始飙升的。这些“证据链”比单张告警截图有用得多。日志要定期压缩清理我一般用logrotate或者干脆写进 Prometheus 里按月保留能查多久看你的磁盘容量。最后分享一个从这轮踩坑里总结出的习惯监控上线后的第一周所有告警先发到测试群不要直接轰生产群。第一周观察的是“规则是否合理”不是“故障是否真实”。等一周下来误报率降到一个你能接受的水平再让告警去骚扰真正值班的人。这样既不会把噪音扩散给团队也能在正式上线前把阈值调准。