看板是总结,不是放大镜
一张有用的看板,应该帮助读者决定接下来检查什么。它把大量观测归纳成少数几个问题:用户操作是否成功,影响发生在哪里,资源是否正在逼近限制。但归纳不能抹掉重要区别。全局成功率仍然很好看时,一个区域可能已经严重失败;面板空白也可能是遥测缺失,而不是服务处于安静且健康的状态。看板的价值在于减少重新推理的负担,同时保留做判断必需的上下文。 给页面安排清楚的阅读顺序 先展示服务结果和观测覆盖,再给出流量、延迟、错误以及有关资源的饱和信息。如果区域、操作类型或依赖的差异会改变下一步行动,就提供对应下钻入口。变更标记和诊断资料链接也应放在相关视图附近。曲线同时变化可以帮助选择调查方向,但仅凭发生时间接近,还不能证明某次部署或某个依赖导致了事故,仍需进一步验证真实链路。 每个面板应该回答明确的问题,但不要求只放一个指标。队列长度与配置上限使用相同单位,并列比较就很有意义;把不同单位放进复杂双轴图,则容易制造没有根据的视觉关系。应直接说明单位、聚合窗口、统计总体以及阈值含义,让读者不必凭记忆猜测坐标表示毫秒、秒、零到一比例还是百分数。含义清楚通常比增加面板数量更重要。 保留请求权重和观测覆盖 计算服务错误比例时,应使用相同窗口中的全部不良事件数除以全部符合条件的事件数。直接平均区域百分比,会让很安静的区域与繁忙区域获得相同权重,这已经换成另一种统计量,不是无关紧要的显示方式。即使全局值按请求正确加权,当小范围用户仍属于服务承诺的一部分时,也需要展示能定位影响的区域或操作细分。 新鲜度同样决定统计范围。应展示最近一次成功观测的时间,或者明确的覆盖信号,并约定何时把陈旧数据视为未知。业务看板选择的新鲜度阈值,与存储系统处理时序失效的机制是不同概念。把缺口一律补零,可能让已经不可用的采集器表现成完美错误率或空队列。缺失状态应被保留下来,并提供下一步排查采集链路或服务本身的方向。 坏掉的样子 自动缩放的纵轴可能把微小变化放大成重大事故,过宽的固定范围又可能掩盖真正越过阈值的变化。应根据运维问题选择刻度,让需要比较的面板保持一致。平均延迟还会藏起慢请求群体,需要合适的分布与观测数量作为补充;不能直接平均不同实例已经计算出的高分位数,再把它当成整个服务的尾部延迟。 外观漂亮的页面也可能只是一些互不相关的快照。时区、过滤条件、刷新频率和聚合窗口不同,会妨碍连贯比较。共享变量应有明确默认值和可见范围,尤其要让读者知道当前是否查看生产环境以及哪些区域。应拿区域故障、低流量和观测缺失来检查看板,如果三种场景都表现为绿色,就说明页面在压缩信息时丢掉了做决策所需的关键事实。 快速验证 将代码保存为 dashboard.py 并用 Python 3 执行。它基于一个完整六十秒窗口中的模拟计数,计算可以用于面板展示的值。两个区域有新鲜观测,另一个区域既缺少数值,最近观测时间也较旧。九十秒阈值是示例看板的业务规则,并不是 Prometheus 默认行为;验证这部分聚合逻辑不需要安装 Grafana,也不会操作任何现有仪表盘。 import json from fractions import Fraction # Counts cover the same completed 60-second interval. rows = [ {"region": "west", "requests": 9800, "errors": 0, "age_seconds": 10}, {"region": "east", "requests": 200, "errors": 100, "age_seconds": 10}, {"region": "south", "requests": None, "errors": None, "age_seconds": 240}, ] fresh = [r for r in rows if r["age_seconds"] <= 90 and r["requests"] is not None and r["errors"] is not None] missing = [r["region"] for r in rows if r not in fresh] requests = sum(r["requests"] for r in fresh) errors = sum(r["errors"] for r in fresh) regional = {r["region"]: Fraction(r["errors"], r["requests"]) for r in fresh} known_ratio = Fraction(errors, requests) unweighted = sum(regional.values()) / len(regional) assert known_ratio == Fraction(1, 100) assert regional["east"] == Fraction(1, 2) and unweighted == Fraction(1, 4) assert missing == ["south"] print(json.dumps({ "coverage": "incomplete", "fresh_requests_per_second": round(requests / 60, 3), "fresh_error_percent": float(known_ratio * 100), "regional_error_percent": {k: float(v * 100) for k, v in regional.items()}, "wrong_unweighted_percent": float(unweighted * 100), "unknown_regions": missing, }, indent=2)) 新鲜观测合计一万次请求、一百次错误,对应每秒约一百六十六点六六七次请求,以及按请求加权后的百分之一错误率。然而流量较小的东部区域有一半请求失败。若直接平均两个区域的百分比,会错误地把合并请求错误比例写成百分之二十五。正确方式是让百分之一的总体值与区域细分共同出现,而不是挑选某一个数字来遮住另一个事实。 ...