凌晨两点,手机警报像催命一样响起来,我眯着眼摸到手机,屏幕上跳着“MySQL主库连接数超阈值”。脑子瞬间清醒,翻身起床开电脑,一顿操作猛如虎,连上去一看,连接数确实突破了阈值,但再仔细一查——好嘛,是监控脚本自己连的库没有释放,加上业务凌晨的定时任务刚好跑起来,两个因素叠一块儿,把连接数顶过了线。DB本身稳如老狗,QPS、慢查询、锁等待一个异常都没有。把监控脚本的连接池改小,问题消失,我却损失了一夜睡眠。
这种事儿干多了,谁都会忍不住骂一句:DB监控告警的道理我全明白,指标、阈值、通知、降噪,说起来头头是道,但真到落地的时候,不是误报刷屏没人看,就是真出故障了告警没响,最后背锅的还是自己。“老明白了但就是搞不好”不是不聪明,而是没人告诉你那些文档里不写的坑。这个系列我就准备把这些坑一个个扒开,从最常挨骂的监控告警聊起,把我自己踩过的、看别人踩过的、以及最后怎么绕过去的经验都摆出来,希望能让正在被告警折磨的你少走点弯路。
1. 告警为什么总挨骂:先搞清楚你踩的是哪个坑
1.1 不是不懂,是没把“告警”当产品设计
很多人一听到“告警设计”就觉得虚,觉得告警嘛,不就是设个阈值、超过就发消息吗?但实际做下来你会发现,告警系统跟业务系统一样,需要从需求、场景、用户、体验四个维度去设计。需求是业务方和DBA真正关心什么,场景是故障发生时谁需要知道什么信息,用户是值班工程师、研发负责人还是老板,体验则是这条告警发出来之后,收到的人能不能一眼看懂、能不能直接行动。
我见过太多团队把监控告警做成了“数据搬运工”:写几个SQL,查一下performance_schema或者pg_stat_database,然后拿固定值跟指标比,超过就Alertmanager发到钉钉/企微群。这不能算错,但离“好用”差距极大。真正的告警设计,应该像做产品一样:先定义清楚“这个告警是给谁看的、解决什么问题、触发后期望他做什么”。比如MySQL主从延迟告警,发给DBA和发给研发,措辞和附带信息完全不同。DBA需要知道延迟秒数、复制线程状态、最近 relay log 情况,研发只需要知道“哪个业务的读流量可能受影响,应用侧要不要降级”。做不好告警的团队,通常都是把这两类人丢到同一个群里,发同一条没头没尾的消息。
你去看那些告警做得好的团队,他们的告警规则文档长得跟产品需求文档一样,每条规则都有负责人、触发条件、影响范围、处理预案、恢复通知。我后来才意识到,之前老挨骂,不是不会配Alertmanager的route,而是压根没把告警当一个正经系统来设计。这个认知的转变比学任何工具都重要。
1.2 常见挨骂场景对照:你以为的监控 vs 实际的监控
我总结了几种典型的“挨骂现场”,几乎每个团队都经历过。第一种叫“狼来了”,告警天天响,但大部分是误报,时间长了,真出问题也没人看。第二种叫“后知后觉”,业务方先发现页面打不开了,跑过来问你数据库是不是挂了,你这才上去看监控,发现慢查询已经堆成山。第三种叫“互相甩锅”,告警里信息太少,只说“CPU使用率>90%”,研发问是哪个业务的实例,你答不上来,对方一句“你监控怎么做的”怼得你哑口无言。
这三种现场的本质,都不是监控工具不行,而是“监控指标”与“用户感知”之间缺了一座桥。你以为监控了数据库的CPU、内存、连接数、QPS、慢查询,就是监控了数据库。但业务方感知到的“数据库挂了”是“接口超时”“页面打不开”“订单提交失败”,这两者之间的映射关系如果没人去维护,告警自然就成了摆设。
所以后来我改了一版监控看板,把业务侧的接口错误率、平均延时和DB侧的指标放在同一个时间轴上。业务告警和DB告警一起看,谁依赖谁、谁引发谁,一目了然。这么做之后,研发同事对我的态度明显好了很多。监控告警不是只给自己看的仪表盘,而是跨团队协作的沟通工具,这句话我现在越来越认同。
2. 监控做不好的根子:指标、阈值、通知这三层全断了
2.1 指标层:你以为的DB指标和实际业务差着十万八千里
先聊聊指标选型。很多人配置监控项,习惯照着网上的文档或者云厂商的默认模板来,MySQL就监控QPS、连接数、InnoDB缓冲池命中率、主从延迟,PostgreSQL就监控事务数、死锁数、缓存命中率。这些指标不能说错,但如果你问一句“这些指标反映业务什么状态”,好多人都答不上来。
举个例子,连接数高一定代表数据库有问题吗?不一定。我遇到过应用侧连接池配置了最大值200,但业务高峰期并发一上来,连接池被打满,应用报“无法获取连接”,数据库端的连接数看起来只是正常偏高,但业务已经卡死了。这时候你监控连接数的意义就不大,真正需要监控的是应用层“活跃连接数占连接池比例”以及“获取连接耗时”。这属于应用监控,但DB监控要是不懂这些,就会在排查时把方向搞反。
监控指标必须从业务链路里“长”出来。我现在的习惯是,接一个新的数据库实例之前,先画一张简单的调用链路:客户端->负载均衡->应用->连接池->DB。然后逐个节点想:这个环节要是慢了,DB层面会有什么表现?应用层面会有什么表现?哪个指标能最先感知到?比如订单系统,用户下单写库,核心链路是“插入订单表+扣减库存”。那监控重点就应该是这两个表的行数变化、锁等待时间、binlog写入量,而不是泛泛地看整个实例的TPS。只有指标和业务场景咬合在一起,告警才有意义。
2.2 阈值层:静态阈值是原罪
指标选得再准,阈值设得不对也是白搭。最常见的翻车姿势是拍脑袋定阈值:CPU超过80%告警、内存超过90%告警、连接数超过500告警。看似没毛病,实则漏洞百出。因为不同数据库、不同业务、不同时间段,指标的“正常范围”完全不一样。核心交易库CPU常年80%跑着没事,一个内部报表库CPU飘到40%就可能出问题。你用一个固定阈值去套所有实例,结果就是误报和漏报并存。
静态阈值的另一个问题是没考虑时间维度。很多指标有典型的周期性,比如电商业务白天高、凌晨低,工作日的波峰和周末完全不是一回事。如果阈值只在“全天任何时间超过X就告警”,那白天可能高频误报,凌晨真出问题反而被淹没。我踩过最惨的坑是给一个批处理库设了“活跃会话>30”的告警,平时凌晨2点跑批,活跃会话能冲到80,因为天天报,值班的人直接忽略了这个规则。后来有一天跑批卡死,活跃会话卡在200,群里的告警飘了半小时没人处理,直到业务方打电话来。
解决思路是让阈值跟着时间走。最简单的做法是按小时设置不同的阈值,比如业务高峰期连接数阈值设300,低谷期设150。更聪明一点的是引入基线告警,用过去14天的历史数据预测当前时刻的合理范围,超过基线一定比例才触发。Prometheus + 机器学习或者一些商业APM工具都支持这个能力,开源方案也有类似的东西。这里要提醒一句:基线告警刚上线时误报特别多,因为模型还没学到业务的突发模式。建议先观察一段时间,把基线的敏感度调到一个既能发现异常、又不至于天天吵人的程度,再逐步放开。
2.3 通知层:告警疲劳与告警轰炸
前面两层做得再完善,通知层不懂“降噪”,照样挨骂。告警疲劳这个词各位肯定不陌生:当一个群里每天刷几百条告警,人就会自动选择无视,大脑会把它们过滤成背景噪音。等真正的故障告警出现时,没人注意到,值班群变成了“比谁眼神好”的游戏。
告警轰炸常见有三种来源。第一是重复告警,同一个问题每5分钟发一次,一小时12条,全是同样的话。第二是抖动告警,指标在阈值边界来回横跳,触发-恢复-再触发,把人的情绪一起搞崩。第三是风暴告警,一个根因导致多个指标异常,于是CPU、内存、连接数、锁等待、慢查询一起报,一个故障产生几十条告警,真正有用的根因信息被淹没。
要治理告诉疲劳,我的经验是三层filter:第一层,在产生端去重。同一实例同一指标在未恢复前只发一次,后续所有变化都走“update”而不是“create”。第二层,加“持续时长”条件。比如CPU>90%持续5分钟再告警,这能过滤掉大量瞬时抖动。很多监控系统都能配“for”参数,但真正用的人不多。我在团队里立了个规矩:没有“for”条件的告警规则一律不许上线,宁可延迟一两分钟,也要保证告警是“值得响”的。第三层,聚合和分级。告警不能一视同仁,要分P0/P1/P2,P0直接电话+短信,P1发钉钉/企微并@值班人,P2汇总成日报。Alertmanager里的route + inhibit + group_wait就是干这个的,配置对了,告警量能下降70%还不漏真故障。
3. 一套能少挨骂的DB监控告警落地参考
3.1 指标选型:从业务视角倒推监控项
我自己沉淀了一套指标选型的方法,不一定适用所有团队,但至少能让你少走弯路。第一步,先把数据库实例按角色和业务重要性分成几类:核心交易库、普通业务库、报表分析库、中间件库(比如Redis、ES背后的数据源)。不同类别的库,监控密度和告警阈值都应该不一样。核心交易库每5秒采集一次,P0告警;普通业务库每30秒采集一次,P1告警;报表库甚至可以不配实时告警,只配看板。
第二步,对每个分类,用“黄金信号”框架来选指标。数据库的黄金信号我总结为:可用性(能不能连上、进程在不在)、容量(磁盘、内存、连接数是否接近上限)、延迟(SQL响应时间、事务执行时间)、错误(死锁、锁等待超时、复制中断)。这四个维度每个维度挑1-2个最核心的指标就够了,不要贪多。
比如MySQL核心库,我最终保留的监控项是这些:
- 可用性:实例存活(TCP连通+SELECT 1)、主从复制状态(Seconds_Behind_Master)
- 容量:磁盘使用率、连接数使用率(当前连接数/max_connections)
- 延迟:平均查询耗时(按业务维度拆)、慢查询数(>1s)
- 错误:锁等待事件次数、死锁次数、复制线程停止状态
每个指标都要回答一个问题:“它异常时,业务会怎么样?”答不出来的指标,直接删掉。这套筛选下来,一个MySQL实例的监控项一般控制在15个以内,告警规则5-8条,已经能覆盖90%的故障场景。
3.2 阈值动态化:用基线和趋势代替固定值
配置阈值的时候,我强烈建议优先用“趋势”和“基线”来替代绝对值。绝对值的问题是业务增长和资源规划一变,老阈值立刻失效。比如半年前连接数200就算高,半年后业务涨了,连接数常年400,你再守着200的阈值,那真是天天告警。
趋势告警的做法是,对你关心的指标做一次和过去24小时/7天同时间段的对比。如果当前值显著超过历史同期(比如超过3倍),说明出了异常。这种告警的优点是能自动适应业务增长和周期性波动,缺点是配置起来比固定阈值复杂。但我说句实话,这世界上没有既简单又准确的告警,你不想被骂,就得在配置上多花点心思。
具体到实现,我之前用Prometheus + PromQL 里的deriv()和predict_linear()做过简单预测。比如对于磁盘用量,可以用过去6小时的斜率预测未来4小时会不会打满,会的话就提前告警。这比单纯设置一个“磁盘>90%”的阈值要智能得多。对于需要基线的地方,我习惯维护一个“业务高峰日历”,把双11、大促、月末结账这种特殊日期标出来,当天自动把阈值上调一定比例。
3.3 告警降噪:聚合、去重、分级、路由
降噪是告警系统里最能直接体现“经验”的部分。我见过不少团队,Alertmanager配置了,但只会用最基础的group_by: ['alertname'],导致下游还是被轰炸。我的降噪配方分四步:
第一步,去重。同一实例同一告警规则在状态恢复之前,只发送一次。后续状态变化走“已持续X分钟”的更新消息,但不再新增一个独立的告警。这一步能减少40%的条数。
第二步,聚合。把“同一个根因”触发的所有告警合并成一条。比如数据库连接池耗尽,可能同时触发连接数告警、活跃事务告警、应用获取连接超时告警。在Alertmanager里用group_by: ['cluster', 'instance']按实例聚合,再把常见的关联告警做inhibit规则,比如“活跃事务告警发生时,抑制同一实例的慢查询告警”,因为这大概率是一个根因引起的一系列症状。
第三步,分级。我参照Google的SRE实践,把告警分成三类:Page(立即通知值班人,要求5分钟内响应)、Ticket(发到工单系统,当天处理)、Log(记到日报里,每周复盘)。分级要由DBA团队和研发团队一起定,不能只让监控管理员拍脑袋。定级标准是“业务受影响程度”而不是“指标偏离程度”。比如慢查询多,但DB响应还在业务容忍范围内,那就只是Ticket;如果慢查询导致核心接口P99超过200ms了,才算Page。
第四步,路由。不同告警要发给不同的人。核心库的Page告警打值班DBA电话,同时@研发接口负责人;非核心库的告警进群就行,不要电话骚扰。路由配置在Alertmanager的route节点里做,规则要尽量细化,宁可多写几行也要把“谁负责什么”分清楚。
3.4 告警自愈:能自动处理的别打扰人
自愈是降噪的进阶版,也是“不挨骂”的杀手锏。很多告警其实是可以自动处理的,但如果你不写自愈脚本,就只能靠人肉看。我做过一个典型例子:MySQL复制中断。以前复制线程停了,监控发现后发告警,值班人上来先登录实例,看错误日志,然后重新start slave,还要再验证Seconds_Behind_Master慢慢追上来。流程跑完至少10分钟,如果发生在凌晨,值班人第二天都是黑眼圈。
后来我写了一个自愈脚本,监听复制中断的告警webhook,自动执行以下步骤:先检查错误码,如果是常见的1236(binlog被清理)、1062(主键冲突跳过)这类可预期错误,直接按预案处理——跳过事务、重新同步位点、恢复复制。处理完了自动发一条通知:“已自动恢复,耗时XX秒,错误原因XXX”。如果是不认识的错误码,才转人工Page。这样一个季度下来,复制中断告警的处理时长从平均20分钟降到了2分钟,值班同事终于能在凌晨睡个整觉。
自愈不是万能药,但凡是那些“操作固定、风险可控”的告警,都应该尽量自动化。我自己在团队里推了一个规矩:一条告警规则上线时,必须同时写清楚“如果触发,谁来处理、手动操作步骤是什么”。如果这个操作可以在脚本里描述清楚,那就直接做成自愈。做不到的再走Page。
4. 实操中踩过的坑和排查实录
4.1 典型案例:连接数告警刷屏,最后发现是连接池配置问题
有一阵子我们某个业务库每天晚上固定时间连接数飙升,告警刷屏。我一开始怀疑是慢查询拖住了连接,上去一看,慢查询不多,但活跃会话也没异常。后来把时间点对齐才发现,每天晚上21:00有个定时任务会批量从应用拉取数据,而应用侧的连接池配置是maxTotal=500,空闲连接回收时间又设得特别长。深夜批量任务一跑,应用短时间内申请大量连接,但连接池不会马上归还,导致DB端连接数堆积。
这个问题的根结不在DB,而在于连接池参数。排查完之后我们做了三件事:把连接池的最大空闲时间调短,加了testWhileIdle;给定时任务单独设了一个小的连接池实例;DB侧连接数告警加了一个“持续5分钟”的条件。从此这个告警再也没半夜响过。复盘的时候我们意识到,很多DB告警其实是应用行为不当的信号,单纯在DB侧加宽阈值是治标不治本。连接数告警响了,第一反应应该是看“是谁发起的连接”,而不是急着提DB的max_connections。
4.2 典型案例:慢查询告警天天有,但业务根本不卡
另一个经典翻车案例是慢查询告警。我们有个库,每天慢查询数超过1000条的告警几乎持续触发,但业务方反馈毫无感知。我一开始也困惑,后来把慢查询日志捞出来看,发现90%的慢查询都是同一类后台统计SQL,扫描全表几百万行,单条执行2秒。但这SQL跑在只读从库上,而且只在空闲时段执行,从库本身压力不大,业务读请求都是走另一套缓存,所以用户根本感觉不到。
这个案例逼我反思:“慢查询”到底该怎么定义?单条SQL执行超过1秒就算慢,但对一个离线统计来说,2秒完全能接受。后来我调整了策略:按SQL类型分开监控,线上核心链路的SQL执行超过200ms才告警,后台分析类的SQL以执行计划是否变化、是否索引失效为准。告警规则细化之后,这个库的慢查询告警量从每天几十条降到了一周两三条,而且每一条都是真正需要看的。
从这里我总结出一个排查技巧:任何告警触发后,先不要立刻去看DB状态,先看“这条告警对应的业务活动是什么”。从业务事件倒推,找到“为什么这个时候这个指标会异常”,比盯着指标本身更容易定位根因。
4.3 排查技巧:快速定位是监控问题还是DB问题
当一条看起来不合理的告警出现时,我自己按下面这个顺序排查,基本能在10分钟内确定是DB真有问题还是监控数据搞鬼。
第一步,查数据链路。告警里的指标值是从哪采集的?是Prometheus直接抓的,还是通过exporter转了一层?exporter本身有没有异常?我遇到过exporter因为权限问题,拿不到processlist,返回空数据,导致“活跃连接数”被统计成0,反而触发“实例不可用”告警的事。所以看到诡异的指标,先怀疑采集端。
第二步,交叉验证。用另一套独立的工具去查同一个指标。比如Prometheus说连接数800,就用命令行直接登录MySQL执行show processlist;看看实际进程数是不是差不多。如果两个数据对不上,通常监控侧的问题,不要急着动DB。
第三步,看时间相关性。把DB告警和业务请求量、应用日志错误叠在同一个时间轴上。如果告警时间和业务波峰吻合,指标本身没有异常增长,那就是阈值设太紧了;如果告警时间和某个应用发版时间吻合,十有八九是应用改动带来的行为变化,去查那个发版的代码。
这套流程走下来,基本不会漏判。最怕的是看到告警就紧张,直接去重启数据库——重启一时爽,排查火葬场,故障原因没找到,下一次告警还会来。
5. 从“老挨骂”到“被认可”的几个习惯
5.1 告警规则要写变更记录
告警规则不是配好就不动了,业务在变,数据量在变,实例数量在变。如果谁改了告警配置都不说,等下次告警爆出来,后人根本不知道这条规则当初为什么这么设。我给团队定了一个规矩:所有告警规则的增删改都必须走一个轻量级的变更评审,至少要在群里@一下相关人员,说明“改了什么、为什么改、期望达到什么效果”。
这个习惯救过我一次。有一次磁盘告警阈值从80%调到90%,结果没过多久磁盘真的涨到85%,但因为有调整记录,大家知道这是临时放宽,等运维扩充磁盘后才恢复正常。如果没有记录,这个时间段里磁盘85%没告警,万一业务出问题,第一个被怀疑的就是“为什么告警没响”。有了变更记录,即使最后发现阈值调错了,也能快速回溯是谁在什么场景下做的决定。
5.2 每周做一次告警复盘
我每周五下午雷打不动做一件事:拉出这一周所有的告警记录,逐条过一遍。哪些是误报、哪些是重复告警、哪些是真正发现问题的。误报的,找原因,能优化规则就优化,不能优化就标注“已知现象,无需处理”。真正发现问题的,复盘处理过程有没有可以改进的地方,比如发现时间是不是太晚、告警信息是不是不够清晰、处理预案是不是没跟上。
复盘的好处不是当场能解决多少问题,而是逼着大家去思考“这些告警存在的价值”。坚持做一个月,你会发现告警量肉眼可见地下降,因为重复的规则被合并了,低价值的规则被删除了,真正重要的规则被加粗了。更重要的是,团队里每个人对“什么样的告警值得响”逐渐形成共识,以后业务方收到告警也不会直接跑来质问“这又是什么鬼”,因为他们知道,能发出来的都是过过脑子的。
5.3 给业务方一份“告警解释手册”
最后一招,也是我真心推荐的:给经常合作的产品、研发、运维同事写一份通俗版的《数据库告警解释手册》。不要写技术参数,就写“当你收到这条告警时,意味着什么,你可能需要做什么”。比如“连接数过高”这条,手册里写:数据库当前同时服务的连接数快达到上限了,通常是应用连接池配置不当,或者某个业务SQL卡住不释放连接。请先检查你的应用日志里有没有获取连接超时的报错,如果有,请联系DBA提供show processlist截图。
这份手册看起来原始,但效果奇好。以前业务方一收到告警就问我“数据库是不是挂了”,现在他们自己会先对照手册做一个初步判断,来问我的时候已经带着应用侧的信息了,沟通效率高了一个量级。我始终觉得,监控告警不只是技术手段,它是一种跨团队的语言。你把告警解释清楚了,大家才能用同一种语言协作,否则永远是在互相猜疑中挨骂。
我个人在实际操作中的体会是,DB监控告警这条路,没有一劳永逸的配置,也没有万能的开箱即用工具,它更像是一个持续迭代的过程。你不必一开始就追求完美,但一定要在每一次误报和每一次漏报中记下一笔,然后逼着自己去调整。今天被骂“监控怎么做的”,明天就能拿出“我改了哪些规则、为什么改、带来了什么效果”的底气。这个系列后面我还会继续写一些具体的工具选型和配置示例,但如果你能把上面这些思路先吃透,那就已经超过大半只会机械配阈值的同行了。