绝大多数情况下不建议自研告警通知组件,应优先复用 Prometheus Alertmanager 等成熟方案;仅当满足定制渠道、严苛性能及可控运维三条件时才考虑自研。

告警通知组件该不该自己写
绝大多数情况下,不建议从零实现告警通知组件。Go 生态已有成熟、轻量、可嵌入的方案(如 go-kit/log 配合 prometheus/alertmanager 接口,或直接复用 github.com/prometheus/alertmanager/template 的渲染逻辑),自研容易陷入重试策略、渠道降级、敏感信息脱敏、模板热加载等隐性复杂度里。
只有当满足以下全部条件时,才值得动手:
• 告警渠道极其定制(比如只发内部 IM 协议,且无标准 webhook)
• 对延迟和内存占用有硬性要求(• 现有库无法满足灰度开关、多租户隔离等业务强约束
用 alertmanager.Client 直接对接 Prometheus 生态
如果你的系统已接入 Prometheus,这是最省心的路径——复用 Alertmanager 的路由、抑制、静默能力,Go 侧只需负责发送符合其 API 规范的 Alert 结构体。
关键点:
立即学习“go语言免费学习笔记(深入)”;
-
alertmanagerv0.26+ 提供了官方客户端github.com/prometheus/alertmanager/api/v2/client,但实际更推荐直接 HTTP POST,避免引入大依赖 - 请求地址通常是
http://alertmanager:9093/api/v2/alerts,注意路径末尾是alerts(不是alert) - 必须设置
Content-Type: application/json,否则返回415 Unsupported Media Type - 每条
Alert至少需包含labels(map[string]string)和annotations(map[string]string),空labels会导致 Alertmanager 拒收
data := map[string]interface{}{
"alerts": []map[string]interface{}{{
"status": "firing",
"labels": map[string]string{"job": "api", "severity": "critical"},
"annotations": map[string]string{"summary": "HTTP error rate > 5%"},
"startsAt": time.Now().UTC().Format(time.RFC3339),
}},
}
jsonBytes, _ := json.Marshal(data)
resp, _ := http.Post("http://am:9093/api/v2/alerts", "application/json", bytes.NewBuffer(jsonBytes))
自研通知分发器要守住三个边界
如果真得自己写,核心不是“怎么发”,而是“怎么不乱发”。重点控制以下三处:
-
渠道熔断:对每个通知渠道(email / slack / dingtalk)维护独立错误计数器,连续 3 次
5xx或超时(>10s)自动禁用 5 分钟,避免雪崩 -
频率压制:按
labels{job,instance}组合做滑动窗口限流(例如 10 分钟最多 5 条),防止同一故障刷屏;别用全局计数器,会误伤 -
上下文剥离:告警原始数据(如 traceID、podIP)必须经过白名单过滤才能进
annotations,禁止直接透传env、secret类字段,否则可能泄露凭证
钉钉/企业微信通知的签名坑
国内 IM 渠道的签名验证极易出错,不是算法问题,而是时间与编码细节:
- 钉钉 Webhook 的
timestamp必须是毫秒级 Unix 时间戳(time.Now().UnixMilli()),不是秒级,否则签名永远失败 - 签名原文拼接格式为
timestamp + "\n" + secret(注意换行符是\n,不是\r\n) - 企业微信的
agentid和corpid区分大小写,且corpid是 URL 中的xxx部分,不是管理后台显示的“企业 ID”全称 - 所有 HTTP 请求头必须小写(
content-type),某些 SDK 会自动转成Content-Type,导致企业微信返回40025 invalid signature
这些细节在文档里藏得深,但一次签名失败就会卡住整条告警链路,调试时优先打日志确认拼接字符串和 header 大小写。


















