在线上环境里,一个后台进程可能因为内存泄漏、死锁或第三方依赖超时而悄悄退出,等到业务方反馈往往已经造成损失。使用Golang构建一个轻量的进程监控器,可以在单二进制文件内完成系统信息采集、阈值判断与报警通知,既方便部署又易于扩展。

一、监控器整体设计思路
一个基础的Golang进程监控器通常包含三个模块:采集器、判定器和通知器。采集器负责周期性获取目标进程或宿主机的资源数据;判定器将采集值与预设阈值比较,决定是否触发异常;通知器把异常信息通过邮件、Webhook或日志对外发出。这种分层结构让每一部分可以独立替换,比如后期把gopsutil换成更底层的syscall实现,而不影响报警逻辑。
在架构上,建议将监控器自身也做成一个独立进程,通过进程ID(PID)去 attach 目标程序,避免和业务进程耦合。如果目标进程崩溃,监控器依然存活并可以记录最后一次状态,甚至尝试拉起服务。下面先用一段代码展示如何按PID获取进程对象。
package main
import (
"fmt"
"github.com/shirou/gopsutil/v3/process"
)
func getProcess(pid int32) (*process.Process, error) {
// 根据PID构造进程结构体
p, err := process.NewProcess(pid)
if err != nil {
return nil, err
}
return p, nil
}
func main() {
p, err := getProcess(1024)
if err != nil {
fmt.Println("进程不存在或无权访问")
return
}
fmt.Println("找到进程:", p.Pid)
}
二、系统信息采集的实现
系统信息采集分为两类:一类是进程级指标,例如某个PID的CPU占用率、常驻内存、打开文件数;另一类是主机级指标,如整机负载、磁盘剩余空间。Golang生态中,gopsutil库封装了各平台差异,在Linux、Windows、macOS下都能用同一套接口拿数据,大幅降低开发成本。
以进程内存为例,gopsutil的MemoryInfo方法会返回包含RSS、VMS等字段的结构体。RSS即实际物理内存占用,是判断泄漏最常用的指标。需要注意的是,频繁调用采集接口本身也会消耗CPU,因此采集间隔不宜低于1秒,生产环境一般设5到10秒一次。下面的代码演示如何读取并输出内存与CPU使用率。
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/v3/process"
)
func collect(p *process.Process) {
// 获取内存信息
mem, err := p.MemoryInfo()
if err == nil {
fmt.Printf("RSS: %d KBn", mem.RSS/1024)
}
// 获取CPU使用率,间隔1秒
cpu, err := p.CPUPercentWithContext(time.Second)
if err == nil {
fmt.Printf("CPU: %.2f%%n", cpu)
}
}
func main() {
p, _ := process.NewProcess(1024)
for {
collect(p)
time.Sleep(5 * time.Second)
}
}
如果出于安全或性能考虑不想引入第三方库,也可以直接使用proc文件系统。在Linux上读取/proc/<pid>/statm就能拿到页面数,再乘以系统页大小即可得到内存占用。这种方式零依赖,但可移植性差,仅推荐在受控的容器或主机环境使用。
三、报警机制与阈值判定
判定逻辑应当支持静态阈值和动态基线两种模式。静态阈值最简单,比如内存超过512MB就报警;动态基线则根据过去一小时平均值浮动,适合流量有周期波动的服务。无论哪种,都建议在触发前做连续多次确认,防止瞬时抖动产生误报。
通知器可以抽象为一个接口,便于接邮件、钉钉或企业微信。下面给出一个极简的Webhook通知实现,通过HTTP POST把JSON报警体发出去。注意代码中URL使用了ipipp.com做示例域名,实际接入时替换成自己的网关地址即可。
package main
import (
"bytes"
"encoding/json"
"net/http"
)
type Alert struct {
PID int32 `json:"pid"`
Reason string `json:"reason"`
Value string `json:"value"`
}
func sendAlert(a Alert) error {
body, _ := json.Marshal(a)
// 示例地址,实际请改为内部网关
resp, err := http.Post("https://hook.ipipp.com/notify",
"application/json", bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func main() {
_ = sendAlert(Alert{PID: 1024, Reason: "内存超限", Value: "600MB"})
}
在判定器里组合采集与通知,就形成了闭环。例如当RSS连续三次超过阈值,才调用sendAlert。同时应在本地写一份告警日志,防止网络通知失败导致漏报。对于必须立即处理的致命错误,监控器还可执行exec.Command重启目标进程,但重启策略要谨慎,避免雪崩。
四、完整监控循环示例
把前面模块拼起来,就是一个可运行的监控器骨架。它每隔五秒采集一次,发现内存超阈值且连续两次超标就发Webhook。你可以把这个程序交叉编译成无依赖二进制,用systemd托管,实现自启动与自愈。
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/v3/process"
)
func main() {
pid := int32(1024)
p, err := process.NewProcess(pid)
if err != nil {
fmt.Println("目标进程不存在")
return
}
overCount := 0
for {
mem, _ := p.MemoryInfo()
rssMB := mem.RSS / 1024 / 1024
if rssMB > 500 {
overCount++
} else {
overCount = 0
}
if overCount >= 2 {
fmt.Println("触发报警: 内存连续超500MB")
overCount = 0
}
time.Sleep(5 * time.Second)
}
}
以上代码省略了通知器接入,但结构已经清晰。实际项目中应加上退出信号捕获,保证监控器被停止时能释放资源。通过这种Golang进程监控器,团队可以用很低成本覆盖基础可观测需求,为后续接入Prometheus等更完备体系争取时间。