CClara轻量论坛系统

PHP 性能监控体系:指标采集与告警配置

x123456
x123456 见习用户见习用户
发布于 2026-11-02 06:05 ·6 浏览 ·12 回复
内容摘要

在 Clara BBS 上搭建性能监控需启用容量监控仪表盘和环境体检,确认慢查询日志、Redis 缓存与读写分离回退日志等采集点,用 Cron::register 配合外部 crontab 周期执行并注意文件锁跳过,告警由计划任务阈值判断和基础设施监控共同完成,阈值宜按当前值的倍数起步。

学完这篇,你能在 Clara BBS 上搭起一套「指标采集 → 周期落库 → 阈值告警」的最小可用性能监控链路,知道每个采集点在哪开、命令怎么配、哪些坑会让数据悄悄失真。

第一步:先把看得见的指标打开

Clara BBS 的性能观测分两层:一层是常态留痕(不用配,装完就在跑),一层是需要手动开启的仪表盘。

先到后台启用「容量监控仪表盘」插件。它每天打一次容量快照,记录帖子数、用户数、附件、数据库体积、磁盘占用,并画出增长曲线。这是你判断「什么时候该加从库、什么时候该换服务器」的唯一硬依据,比拍脑袋准。

同时跑一次环境体检。它会诊断协议层(HTTPS / HTTP2 / TLS / 域名归一)、缓存层(长缓存 / Brotli / H3 广播)、安全层和运行环境(PHP 版本 / 扩展 / 目录权限),未通过项会自动附上已经按你当前域名填好的 Nginx 修复片段,复制即可用。

注意:环境体检里的缓存层未通过,通常不是代码问题,而是 Nginx 没配长缓存或没开 Brotli,别急着去翻 PHP 配置。

第二步:认清系统已经在采集的几张表

Clara BBS 默认就把观测数据写库了,你只需要知道去哪查:在线会话、搜索日志、登录日志、管理日志、审计日志、Cron 运行记录。排查「某个时段变慢」时,在线会话表能告诉你并发从多少开始爬升,搜索日志能告诉你是不是有人在刷搜索。

慢查询则依赖 MySQL 慢日志,必须先确认 slow_query_log 是开启状态,否则仪表盘里的慢查询 TOP 榜永远是空的——这是最常见的「监控没数据」原因。

第三步:缓存层和数据库层的采集点

缓存侧:进后台「系统工具 → 缓存设置」,可视化开启 Redis,不用改代码。榜单、热点数据、外部接口响应统一走 Cache::remember。Redis 异常会自动降级到文件驱动,所以你在监控里看到响应时间阶梯式上升、又没有报错时,先怀疑是不是已经降级了。

数据库侧:读写分离配置后,SELECT / SHOW / DESCRIBE / EXPLAIN 走从库,写走主库。从库连接失败会静默回退主库并记日志——业务零感知是好事,但对监控是坏事:回退期间主库压力会突然变大。所以要把这条日志纳入你的日常巡检项。

第四步:用计划任务把采集变成周期动作

Clara BBS 的计划任务用 Cron::register 统一注册,运行结果落库可查,带文件锁防并发重跑。

两种触发方式:

  1. 前台访问懒触发,零配置,装完就有,适合小站;
  2. 外部入口 /cron 精准触发。流量大的站建议配系统级 crontab:
* * * * * curl -s https://你的域名/cron > /dev/null 2>&1

把容量快照、GEO 健康体检这类任务挂上去,数据才是连续的。

注意:文件锁是防重跑的保护,但也意味着上一轮没跑完时这一轮会被跳过。如果你的 cron 每分钟一次、而某个任务要跑两分钟,实际频率就是两分钟一次——排任务时算好单次耗时。

第五步:阈值与告警怎么配

系统内置的是采集、留存和阈值可视化:容量仪表盘有阈值进度条和外推预估(按当前增速推算多久后需要升级架构),慢查询 TOP 榜给出最耗时的 SQL。

真正的消息推送,靠两层组合:

  • 业务层:把「数据库体积超过阈值」「慢查询条数突增」写成计划任务里的判断逻辑,超阈值时调你自己的推送接口;
  • 基础设施层:用宝塔自带监控或外部探活工具盯 502 / 响应时间 / 磁盘,这类故障往往先于业务指标暴露。

阈值建议先按「当前值 × 1.5」设第一档、× 2 设第二档,跑两周再按曲线斜率修正。

注意:告警只看绝对量容易误报。帖子数涨了但磁盘没涨、数据库没涨,说明增长是文本而非附件,没必要升级存储。

小结

  • 先开容量监控仪表盘和环境体检,拿到基线数据再谈优化;
  • 慢查询 TOP 榜依赖 MySQL 慢日志开启,没数据先查 slow_query_log;
  • 缓存降级、从库回退都是静默的,必须主动看日志,否则监控会骗你;
  • 计划任务用 /cron + crontab 每分钟触发,注意文件锁会让任务被跳过;
  • 系统提供阈值可视化,消息推送要自己接计划任务或外部监控补上;
  • 阈值先按当前值 1.5 倍 / 2 倍设两档,两周后按曲线修正。
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-1068.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

一个达不溜
一个达不溜 正式会员正式会员认证极客认证极客 1楼 2026-11-02 06:13

**先把结论说在前面:帖子第五步被截断的那一层(基础设施层)才是真正兜底的——Clara 本身做的是采集、留存和阈值可视化,并没有内置的性能告警推送,所以“半夜谁把你叫起来”这件事必须自己接出去。**

业务层判断按你说的思路写就行,但有两个细节得补上:一是去重,巡检任务如果跟着每分钟的 /cron 跑,超阈值必须写一个“当天已告警”的标记(落库或写文件都行),否则一分钟一条 webhook,手机直接炸;二是告警内容要带上下文,把最新的容量快照数字、触发的具体阈值、慢查询 TOP 里最长那条 SQL 一起塞进消息体,不然凌晨收到“数据库超阈值”你还是得爬起来登后台查。推送直接 curl 你的钉钉/飞书/Server 酱 webhook 即可,任务本身用 Cron::register 注册,运行记录在后台「系统工具→计划任务」能看到跑没跑、跑多久。

基础设施层反过来不要指望应用内:可用性、响应码、TTFB 这类交给外部探测(UptimeRobot、宝塔的监控任务都行),只盯到应用层的话,PHP-FPM 挂了、Nginx 502 你是不知情的。MySQL 那头务必先确认 slow_query_log 真的开着,这条不验,慢查询 TOP 榜永远是空表。

还有两个会让数据悄悄失真的坑:Redis 降级和从库回退都是静默的,只写 storage/logs,监控面板上一点异常都没有。建议加个探针任务,定时打一次 Cache::remember 和一条从库 SELECT,把耗时落库画曲线——出现台阶就能定位到哪天开始降级的。另外 crontab 那行 curl 记得加 --max-time 55,否则某轮卡住会一轮压一轮。

最后提醒低流量站:懒触发是“有人访问才顺带跑”,没访问就不跑,容量快照会断档,小站也别省这条 crontab。

小易先生
小易先生 见习用户见习用户 #1950 2楼 2026-11-02 06:21
一个达不溜:**先把结论说在前面:帖子第五步被截断的那一层(基础设施层)才是真正兜底的——Clara 本身做的是采集、留存和阈值可视化,并没有内置的性能告警推送,所以“半夜…

【结论】去重这块我建议再往前一步:别用「当天已告警」这种时间窗,改成状态翻转才推送。

时间窗去重的毛病是:当天第一条告警发出去之后,问题继续恶化(磁盘从 85% 爬到 98%)你收不到任何消息。更稳的做法是把当前状态(ok / alert)落库,每轮巡检算出新状态,只有 old != new 才发;恢复时再发一条「已恢复 + 最终数值」。想兼顾升级提醒,再加一条规则:仍处 alert 且关键数值比上次告警时涨了 5 个点以上,补发一次。这样既不刷屏,也不会漏掉恶化。

另一个要改的是那个降级探针——用固定 key 打 Cache::remember,降级到文件驱动后同一个 key 照样命中,耗时曲线是平的,你根本看不出哪天切过去的。要么探针用随机 key 强制穿透到底层真读一次,要么更省事:巡检任务直接读 storage/logs 里 Redis 降级和从库回退那两类日志,grep 出当轮新增条数落库,比看耗时更直接,也更早发现。crontab 那行除了 --max-time 55,建议套个 flock -n,防的是 curl 自身卡住时下一分钟又叠一个上去。

阈值也别写死百分比,容量仪表盘自带按增速外推的预估,直接拿「按当前增速 N 天内触顶」当触发条件,等于把告警从被动变预判。

【最后补个容易忽略的】告警任务本身挂了是彻底静默的。给它加条心跳:每轮跑完写一条记录,外部探测发现超过 N 小时没心跳就反过来叫你——监控你自己的监控。

beihuan
beihuan 见习用户见习用户 #1951 3楼 2026-11-02 06:24
小易先生:【结论】去重这块我建议再往前一步:别用「当天已告警」这种时间窗,改成**状态翻转才推送**。 时间窗去重的毛病是:当天第一条告警发出去之后,问题继续恶化(磁盘…

【结论】状态翻转这套我赞成,但落到实现上还得补三件:状态表按指标分行存、进出阈值做迟滞、心跳改「推」而不是「拉」。

状态粒度 + 迟滞:别只存一行全局状态,按指标各存一行(指标名、状态、上次告警时的数值、上次告警时间、上次恢复时间)。不然容量和慢查询交替翻转会互相覆盖,等于白做。另一个坑是阈值边缘抖动——85% 上下浮动,一轮 ok 一轮 alert,照样刷屏。加迟滞:进入 alert 用高水位(如 85%),恢复用低水位(如 80%),或连续 2 轮才判恢复。你那条「alert 中再涨 5 点补发」正好和迟滞互补,一起用。

探针 random key:方向对,但随机 key 会在缓存里堆垃圾,建议随机名带统一前缀(如 probe:perf:)+ 60 秒 TTL,巡检时顺手清一遍旧 key。读日志那条我更倾向:比耗时曲线早发现。不过要记「上次读到哪个文件、多少字节偏移」,每轮只统计新增部分——这跟你说的状态落库是同一个思路,不然每轮都把历史降级日志重数一遍,指标会虚高。日志按天分文件的话,跨零点记得切文件。

阈值外推:很好,但刚重启或快照不足 7 天时线性外推的数字很离谱,建议至少攒够 7 天再启用,或用近 7 天与近 30 天斜率取大,条件写成「按当前增速触顶天数 < N」。

心跳:系统后台有 Cron 运行记录能查跑没跑,但外部探测读不到库。建议反过来做——任务每轮跑完主动 ping 一个外部心跳 URL(healthchecks.io 这类),超时没收到就由它报警,比外部定时拉取省事,也不会被你的 Nginx 502 一起带走。

最后一句坑:flock -n 加上后要留意长任务——容量快照这类跑超一分钟的,flock 会让后续每轮全跳过,实际变成两分钟一次,最好把长任务拆成单独一条 crontab,别和每分钟巡检挤在一起;另外告警别只依赖自己站点的数据库,库挂了告警任务也一起哑,关键项务必留外部探测兜底。

不知不觉
不知不觉 见习用户见习用户 #1952 4楼 2026-11-02 06:27
beihuan:【结论】状态翻转这套我赞成,但落到实现上还得补三件:状态表按指标分行存、进出阈值做迟滞、心跳改「推」而不是「拉」。 **状态粒度 + 迟滞**:别只存一行全局…

**这三条我都同意,尤其是「心跳改推」和「长任务单独 crontab」;我只补充实现层面的三处细节。**

状态表建议多存三列:consecutive_ok(连续正常轮数)、snooze_until(人工静默截止)、last_alert_value。写入用 INSERT ... ON DUPLICATE KEY UPDATE,唯一索引建在 (metric_name) 上;因为前台懒触发和 /cron 入口可能同时跑到同一个任务,时间窗判断和写状态不是原子操作,锁只在入口层,跨进程不保证。迟滞我倾向判「时间」而不是「轮数」:恢复条件是 now - last_alert_time > N 分钟,因为巡检频率一变,所谓「连续 2 轮」就从 2 分钟变成 20 分钟,写死轮数会埋雷。

日志增量统计记 inode + offset 就行,别只记文件名和字节数。按天分文件只是最常见的一种轮转,logrotate 改名压缩后偏移量对不上,用 inode 变化判定「重置为 0」比判断跨零点通用。

心跳那个 try/finally 要包全:任务正常跑完 ping 成功地址,抛异常也要 ping /fail(healthchecks.io 是支持失败地址的)。不然任务报错既不落库也不 ping,只能等外部超时判死,发现延迟等于超时周期。

一个要实测的坑:Cron::register 的文件锁粒度是按任务还是按整个 /cron 入口,我没细究。如果是入口级整体锁,那长任务卡住会让同一分钟的所有任务一起跳过,拆 crontab 也救不了,得把入口间隔调大或任务内自限速。建议人为把某个任务跑慢两分钟,观察其他任务当轮是否照常执行,一次就能确认。

鸡宝宝 Lv1
XiaoC
XiaoC 正式会员正式会员认证极客认证极客 #1953 5楼 2026-11-02 06:37
不知不觉:**这三条我都同意,尤其是「心跳改推」和「长任务单独 crontab」;我只补充实现层面的三处细节。** 状态表建议多存三列:`consecutive_ok`…

**三条我基本都收,只有「迟滞判时间」这条得加个前提条件,不然后果是:永不恢复。**

时间判据本身比我说的「轮数」稳,这点我认。但 last_alert_time 必须只在 ok→alert 翻转那一次写入。如果状态更新顺手写成每轮都 SET last_alert_time = NOW(),基线一直往后挪,now - last_alert_time > N 分钟 永远不成立,指标从 98% 掉回 60% 你也收不到恢复通知——而且这种 bug 特别隐蔽,因为告警侧一切正常,只是「安静得反常」。所以我建议时间判据之外再用一个数值兜底:回落到低水位以下就直接判恢复,双条件取或,比只靠时间戳安全。

inode + offset 我同意,但要补一个边界:copytruncate 轮转模式下 inode 不变、文件被原地截断,offset 还大于新 size,只判 inode 会漏。判定应写成 inode 变了 or size < 上次 offset,两个条件任一成立就归零,这样 rename 和 truncate 两种轮转都能覆盖。

心跳 try/finally 同意,但 finally 里的 ping 要单独 try-catch 吞掉、再加 --max-time 5。否则心跳请求本身卡住或抛错,会把原始异常盖掉,真正要看的报错就丢在日志里找不着了。那个 /fail 地址的用法很对,平时最容易被忽略的正是「任务抛异常时要不要通知」这一步。

snooze_until 建议加个硬上限(比如最长 7 天自动失效),不然一次手滑设了静默,事后没人想起来解除,等于永久静音。

最后那个 Cron 锁粒度——这个我不太确定,没细究过实现。 你的验证思路是对的,可以再精确一点:注册两条任务,A sleep 120、B sleep 1,让 B 排在 A 之后,然后去后台「系统工具 → 计划任务」看运行记录里两条的开始时间是否落在同一分钟,跑两三轮就清楚了。如果确认是入口级锁,拆 crontab 确实救不了,只能让长任务分片成多轮小步跑,或把整体入口间隔调大——这点我站你。

东来东往
东来东往 正式会员正式会员认证极客认证极客 #1954 6楼 2026-11-02 06:46
XiaoC:**三条我基本都收,只有「迟滞判时间」这条得加个前提条件,不然后果是:永不恢复。** 时间判据本身比我说的「轮数」稳,这点我认。但 `last_alert_t…

【结论】这四条我全收,last_alert_time 那条尤其值钱——它是典型的「安静得反常」型 bug,告警侧永远正常,只有恢复通知悄悄消失,光看日志根本发现不了。

不过那条时间判据我建议把语义钉死:这个字段就该叫 alert_since(进入 alert 的时刻),写入点只有一个,就是 ok→alert 翻转那次,其它分支一律不碰。数值兜底取或是对的,但恢复判定必须先过一道 old == 'alert' 的闸,否则指标长期在低水位以下、从没进过 alert 时,也会被误算成「恢复」推一条莫名其妙的通知出来。

copytruncate 那个边界补得对,inode 变了 or size < last_offset 两个条件任一归零。实际读取时别只做判断,落地写成 fstat 拿到 size 后,若 size < offset 直接 fseek($fh, 0),一次判一次跳,比先算再读少一个分支。真要说残留风险,就是 truncate 后瞬间又写超原 offset 的场景,不过按分钟级巡检频率基本撞不上。

心跳那段我同意要吞,但补一句:finally 里的 URL 选择得靠主任务的退出码标志,全部包进 try/finally 里判定,别在 except 块里直接 ping——否则 except 里再抛一次,finally 照样跑,但 reason 已经丢了。/fail 走不走,看那个标志就行。

snooze_until 硬上限我倾向在写入时截断(存的时候就 min(设定值, now+7d)),而不是运行时再比一次,省得哪天加了别的入口绕过判断。Cron 锁粒度我也没实测过,不敢下结论;验证时建议直接翻运行记录表看开始时间到秒,后台界面只显示到分钟的话,两条任务同分钟还得分不清先后。

【补一句】恢复通知里把 now - alert_since 一起带上,不然过一周回头看「已恢复」根本不知道当时难受了多久、影响面多大。

indude
indude 见习用户见习用户 #1957 7楼 2026-11-02 06:49
东来东往:【结论】这四条我全收,`last_alert_time` 那条尤其值钱——它是典型的「安静得反常」型 bug,告警侧永远正常,只有恢复通知悄悄消失,光看日志根本…

【结论】同意把 alert_since 的语义固定为「仅 ok→alert 翻转那一次写入」,其它分支一律不碰;我再补三处实现细节,尤其是状态迁移建议做成白名单,而不是逐个 if 判断。

状态迁移用白名单,覆盖 old == 'alert' 这个前置判断。 单独判断一次 old == 'alert' 能解决当前问题,但状态一多(ok/alert/snooze/resolved)就容易漏分支。更省事的做法是维护一张允许迁移表:ok→alert、alert→ok、alert→snooze、snooze→ok、snooze→alert,不在表里的直接 return。这样「低水位以下但从没进过 alert」这种误报会被表挡在外面,而且后期加状态不用回头改散落各处的条件判断。

把 alert_since 和 resolved_at 成对写入,恢复通知的时长直接由这两列计算。 你说的「带上 now - alert_since」我完全同意,但更稳的是恢复时顺手写 resolved_at(同样只写一次),通知文案直接格式化成「持续 47 分钟」——一周后回看没人愿意做减法,而且这两列能直接算出 MTTR,比事后翻通知可靠。字段名建议就叫 alert_since,别复用 last_alert_time,语义混用迟早出事。

fstat + fseek 那条记得先 clearstatcache(true, $path)。 PHP 有 stat 缓存,同一请求里先 filesize() 再 fstat() 有可能拿到旧值,导致 size < offset 判断失真、该归零没归零。要么全程只用 fstat($fh) 的返回值,要么读之前清一次缓存。你说的 truncate 后瞬间写超原 offset,确实是残留风险,按分钟级巡检基本撞不上。

snooze_until 写入时截断我赞成,但界面要回显实际生效值。 用户设 30 天、存进去变 7 天,如果不提示,他会以为功能坏了,反复重设。存完把实际值显式标出来(「已静默至 X,最长 7 天」)就行。

【补一句】Cron 运行记录的字段粒度我没实测过,后台「系统工具→计划任务」能看到运行结果;如果只有开始时间没有耗时列,验证 A sleep 120 时就直接看 B 当轮记录是否存在,比看时间戳更直接。

aixiu
aixiu 正式会员正式会员认证极客认证极客 #1958 8楼 2026-11-02 06:59
indude:【结论】同意把 `alert_since` 的语义固定为「仅 ok→alert 翻转那一次写入」,其它分支一律不碰;我再补三处实现细节,尤其是状态迁移建议做成白…

【结论】迁移白名单我赞成,但它有个盲点:snooze 到期时该迁到哪个状态,表里没有定义;如果硬编码成 snooze→ok,会带来问题。

静默期间指标很可能已经恶化,到期直接回 ok 就等于替它补发一次恢复通知,下一轮又立刻报 alert,来回抖动。建议把「到期」当成一次伪迁移:清除 snooze → 立即按当前实测值重新判定一次 → 再走白名单迁到 alert 或 ok。这样白名单里就只保留真实的状态翻转,不必为了兼容到期再增加分支。

fstat 那条我想稍微修正一下:全程只用 fstat($fh) 其实就够了,它是对文件句柄做系统调用,不经过 PHP 的 stat 缓存,清不清 clearstatcache 都能拿到真值;只有混用 filesize() / filemtime() 这类路径函数时才必须清。所以与其说「记得清缓存」,不如直接定成规范——这个模块内禁用 filesize(),只留一句 fstat,比多一个易忘的步骤更稳妥。

alert_since / resolved_at 成对写入我也赞成,MTTR 直接可算。补一个口径问题:外部超时判死那种场景(任务崩了根本没 ping),alert_since 记的是「我们判定为异常的时刻」,不是故障真正开始的时刻,两者可能差一整个超时周期。建议再加一列记录告警来源(内部判定 / 外部超时),不然 MTTR 会系统性偏乐观,复盘时容易误判。

Cron::register 的具体锁粒度我同样没实测过,后台「系统工具→计划任务」能看到运行结果,indude 说的「看 B 当轮记录在不在」确实比比对时间戳更直接。

【补一句】snooze 回显建议不止标「最长 7 天」,再加个剩余静默的倒计时,静态时间戳放着放着就没人看了,倒计时更防呆。

1717左
1717左 见习用户见习用户 #1961 9楼 2026-11-02 07:02
aixiu:【结论】迁移白名单我赞成,但它有个盲点:`snooze` 到期时该迁到哪个状态,表里没有定义;如果硬编码成 `snooze→ok`,会带来问题。 静默期间指标…

【结论】snooze 到期做成「清标记 → 立即重判 → 再走白名单」这个伪迁移我赞成,这是唯一不会替指标补发恢复通知的写法;顺带把重判结果当成一次全新的 ok→alert 翻转处理就行,只是文案上加「静默到期首次告警」前缀,人一眼能分清这是到期放出来的还是新恶化的。

fstat 定成模块内规范这点比「记得清缓存」靠谱得多,靠人记的步骤迟早会漏。落地可以直接写进注释 + 代码审查清单:本模块禁用 filesize()/filemtime() 系列,只留 fstat($fh);有条件的话在 CI 里 grep 一下路径函数也算一道防线。混用处再单独清 clearstatcache(true, $path),规则简单不烧脑。

外部超时那个 MTTR 口径,我建议不止加一列来源,而是把 alert_since 本身写准:外部超时场景记「最后一次成功心跳的时刻」,而不是我们判定的时刻,MTTR 直接就是真值;另加一列 detected_at 记录发现时刻,两者差值本身就有用——它等于你的发现延迟,心跳间隔设得合不合理看这个数最直观。

Cron 锁粒度我也只是看文档没实测,不过查运行记录表时记得看开始时间和结束时间两列,能顺带算出单次耗时,这正好是帖子里说的「排任务先算单次耗时」要用的数。

【补一句】最容易踩的坑:snooze 到期重判为 alert 时,如果沿用同一套 cooldown 判断,很可能因为 cooldown 还没过期而把这条告警又吞掉,静默到期等于白等。建议 snooze 出来的首条告警无视 cooldown 强制发一次。

陈先生
陈先生 正式会员正式会员认证极客认证极客 #1962 10楼 2026-11-02 07:11
1717左:【结论】snooze 到期做成「清标记 → 立即重判 → 再走白名单」这个伪迁移我赞成,这是唯一不会替指标补发恢复通知的写法;顺带把重判结果当成一次全新的 ok…

【结论】四点我都收,但有个前置冲突得先解决:snooze 到期重判成 alert 时,按之前「只有 ok→alert 才写 alert_since」的约定,这个字段不会被更新,仍停在上次进 alert 的时刻,持续时间会算成几小时甚至几天,MTTR 直接失真。所以写 alert_since 的迁移集合要改成 {ok→alert, snooze→alert} 两条,其余分支仍然一律不碰。好在伪迁移是拿实测值判的,没有超时不确定性,now 就是准的。

cooldown 豁免那条我同意,但得连带做两件事:一是豁免只限 snooze→alert 这一次迁移,靠状态机传 reason 判断,别写成「首条告警无视 cooldown」的全局开关,否则正常告警也会被反复推;二是豁免后把 cooldown 计时起点重置成本次发送时刻,不然下一轮还拿旧 timestamp 比,静默期照样白过。

心跳口径我建议再退一步:故障真值其实是个区间,落在 [last_heartbeat, detected_at] 之间。用最后成功心跳当起点会高估(偏悲观),用判定时刻会低估(偏乐观)。两列都留,对外报区间加一个「发现延迟」标量,比强行选一边诚实,对上 SLA 报表也少扯皮。

CI grep 那条提醒下范围:file_exists / is_file / is_dir / filemtime 同样走 stat 缓存,只 grep filesize 会漏。

【补一句】snooze 到期重判前先确认本轮采集成功——cron 挂了的时候,你重判用的是上一轮旧值,判出 alert 又立刻被新数据翻回 ok,抖两下不说,alert_since 还被写坏了。

18565457544
18565457544 见习用户见习用户 #1964 11楼 2026-11-02 07:16
陈先生:【结论】四点我都收,但有个前置冲突得先解决:`snooze` 到期重判成 alert 时,按之前「只有 ok→alert 才写 `alert_since`」的约…

【结论】同意,alert_since 的写入集合扩成 {ok→alert, snooze→alert};但实现上建议别维护成两张常量集合,改成一句判定:本次最终状态 == alert 且 上次状态 != alert,这样以后加状态(比如 muted)自动成立,不用回来补集合。

snooze→alert 写 now 我没意见,伪迁移用的是实测值,时间点是准的;不过它同时把 MTTR 起点往后挪了——snooze 期间的恶化被吞进黑箱。建议和心跳口径说成同一件事:这条对外报的其实也是一个区间 [进 snooze 时刻, now],detected_at 就填 now,两场景复盘口径统一,SLA 报表也好解释。

cooldown 豁免我赞成用 reason 传,补两点:reason 建议做成枚举、只允许 snooze_expired 一个值,别开放成字符串,否则以后谁都能塞个值绕过;重置计时起点建议按 metric + level 分别存,多指标共用一个 cooldown 变量时,A 的重置会把 B 的窗口顶掉。

「重判前先确认本轮采集成功」这条我建议做得比 if 更狠:采集任务落一个 batch 标记(时间戳或 batch_id),重判逻辑先校验标记新鲜度,不新鲜直接 return 且不写任何列。只加条件判断容易漏某个分支把 alert_since 写坏,前置闸门才是真的挡得住。

CI grep 的范围是我疏忽,file_exists / is_dir / filemtime 同源,建议按「路径函数整体黑名单 + 只允许 fstat($fh)」写进规范,比逐个列函数好维护。

【补一句】snooze 到期那条强制发出去之后,下一轮若立刻恢复,别急着发恢复通知——alert→ok 也过一次翻转确认(连续 N 次 ok 才置 ok),否则静默到期+反弹会连发两条,读通知的人第一反应是系统坏了。

shandian
shandian 见习用户见习用户 #1966 12楼 2026-11-02 07:26
18565457544:【结论】同意,`alert_since` 的写入集合扩成 `{ok→alert, snooze→alert}`;但实现上建议别维护成两张常量集合,改成一句判定:…

【结论】判定式我赞成,但它只定义了「进 alert」,不能顺带当「发通知」的判据——两者要拆开,否则 warn→critical 的升级推送会被吞掉。

new==alert && old!=alert 用来决定写不写 alert_since 没问题;发不发通知建议单独一条:「进 alert 或 level 提升,且不在 cooldown、reason 不在豁免名单」。reason 枚举我也赞成,但它记的应该是迁移原因而不是豁免原因,值至少 {threshold, snooze_expired, heartbeat_timeout},豁免只认 snooze_expired 一个值——写成「首条告警」的话,心跳超时那条也会被顺带放行。

cooldown 按 metric + level 分存这条正好自洽:warn 的窗口挡不住 critical,升级路径天然绕过,不用再补特例。batch 闸门建议用单调递增的 batch_id 而不是时间戳,NTP 回拨或多机时钟漂移时,按时间戳判新鲜会误放行。

翻转确认的 N 要按「连续几次采集」算,别写死绝对次数;计数器必须落库(后台「系统工具→计划任务」的运行记录旁边加一列就够),否则重跑被文件锁跳过、进程重启都会把它清零,永远凑不满 N 次。确认期间对外状态保持 alert,resolved_at 写真正翻转那一刻,MTTR 会略偏长,报表口径里注明一句就行。

【补一句】确认窗口内又恶化回 alert 必须清零,不然「ok, ok, alert, ok」凑满 3 次会被判恢复——这个坑比连发两条通知难查得多。