Skip to content

第 12 讲|观测和发布

系统运行后,日常运维最怕两类信息空白:出问题时不知道发生了什么、发布新版本时不知道影响了什么。前者对应可观测性(Observability)——通过指标、日志、链路追踪、事件让系统状态可见;后者对应发布管理(Release Management)——通过 CI/CD、镜像、灰度、回滚让变更可控。

可观测性和发布管理是现代运维的两大支柱。本讲依次讲清楚:可观察信号的分类、指标的设计、日志的内容要求、链路追踪的工作原理、完整发布链路、灰度发布与回滚策略。

一、可观察信号的四种类型

进程存在不等于服务正常——这是观测性的核心起点:

指标日志链路追踪和事件从不同角度观察系统

  • 用户已经打不开页面,但进程显示 running
  • 接口 P95 延迟飙到 3 秒,但服务"健康"
  • 数据库连接池快被打满,但 CPU 使用率正常

仅看"进程是否存活"远远不够,要从多个维度的信号判断系统真实状态。

四类核心信号

信号记录什么适合回答的问题
Metrics(指标)随时间变化的数值"是不是变差了?""影响范围多大?"
Logs(日志)一条条事件记录"具体发生了什么错?"
Traces(链路追踪)一次请求的完整调用路径"慢在哪一段?"
Events(事件)系统状态变化"什么时候发生了什么操作?"

四类信号配合使用

四类信号各有所长,实际排查问题时是配合着用的。一次"接口慢"故障的标准排查链路:

1. 指标:看到 P95 延迟从 200ms 抬升到 2s
        ↓ "什么时间开始变差?"
2. 日志:在异常时段过滤错误,看到大量数据库相关错误
        ↓ "具体什么错?"
3. Trace:打开一次慢请求的链路,看到时间主要消耗在订单服务到数据库这一段
        ↓ "慢在哪?"
4. 数据库慢日志:看到同一条 SQL 的 Query_time 持续很高,Rows_examined 几十万
        ↓ "根因是什么?"
5. 定位:全表扫描,缺索引 → 加索引解决

每一步都靠对应类型的信号回答,任何一类信号缺失,排查链路就在那一步断掉

二、指标(Metrics)

指标把系统状态量化成数值,适合回答"是不是变差""从什么时候开始""影响范围多大"这类问题。

服务层最重要的几类指标

业界总结为 RED 方法(Rate、Errors、Duration)和 USE 方法(Utilization、Saturation、Errors)。对应到具体指标:

类型指标关注什么
Rate请求量(QPS、TPS)流量是否异常变化(被刷、被攻击、活动开始)
Errors错误率(5xx 比例、业务失败率)错误是否上升
Duration延迟(P50 / P95 / P99)响应时间是否抬升
UtilizationCPU、内存、磁盘使用率资源是否接近上限
Saturation连接池、线程池、队列深度是否有排队、是否要扩容

必须看 P95/P99,不是平均值

延迟这类指标必须看分位数(P95/P99),不是平均值——这是观测性最容易踩的坑。

平均值会遮住尾部慢请求,P95 P99 更接近用户感知

某接口 100 个请求的延迟:
99 个请求 80ms
1 个请求 3000ms

平均延迟:(99 × 80 + 3000) / 100 = 109ms  ← 看起来很美好
P99:3000ms                              ← 真实情况

平均值会被"长尾"拉平,慢请求的痛感完全被掩盖。每 100 个用户里有 1 个等 3 秒,体感非常差——但看平均值你完全感知不到。

分位数的含义:

  • P50 — 50% 的请求快于此值(中位数)
  • P95 — 95% 的请求快于此值
  • P99 — 99% 的请求快于此值
  • P99.9 — 99.9% 的请求快于此值(尾部尤其重要)

监控面板上至少要显示 P50、P95、P99——只看平均值的监控基本是无用的。

告警设计:盯用户感知,不只盯资源

告警要盯着"用户感知的痛苦",而不只是"机器忙不忙"

错误的告警设计:

CPU > 80% → 告警
内存 > 90% → 告警

这种告警的问题:

  • CPU 偶尔飙升但错误率和延迟无变化 → 告警了但实际无影响
  • CPU 不高但应用线程池打满 → 不告警但实际严重影响

正确的告警优先级:

优先级告警内容
P0(立即响应)业务接口 5xx 比例 > 1%、核心链路 P95 延迟翻倍
P1(数小时内响应)资源接近上限(连接池 80%、磁盘 85%)
P2(下个工作日)单实例 CPU 长时间高、慢查询数量上升
监控不告警单实例瞬时 CPU 飙高、网络突发

SRE 的黄金信号(Google SRE Book 提出):延迟、流量、错误、饱和度——这四类是首要告警目标。

Prometheus + Grafana:主流指标方案

云原生时代的标准组合:

  • Prometheus — 时序数据库,定时从应用拉取指标
  • Grafana — 可视化面板
  • Alertmanager — 告警规则与通知

应用暴露 /metrics 端点,内容是文本格式:

http_requests_total{method="GET",status="200"} 12345
http_requests_total{method="GET",status="500"} 23
http_request_duration_seconds{method="GET",quantile="0.99"} 1.234

Prometheus 定期拉取这个端点,写入时序数据库。Grafana 通过 PromQL 查询并展示:

promql
# 5xx 错误率
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

# P99 延迟
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

三、日志(Logs)

日志记录的是具体发生过的事件——具体某次请求、某次错误、某次状态变化。

结构化日志让问题可以被查询和关联

一条有用的日志要回答几个问题

字段作用
时间戳(精确到毫秒)多服务关联、按时间过滤
日志级别(INFO/WARN/ERROR)按重要性过滤
trace_id跨服务串联(关键!)
service / instance知道是哪个服务、哪个实例
method / path定位是哪个接口
statusHTTP 状态码或业务状态
cost_ms请求耗时
user_id / tenant_id影响范围判断
error错误类型和详情

无用日志 vs 有用日志的对比

反面例子(无用日志):

2026-06-23 14:23:01 ERROR: error occurred

这条日志几乎无法排查——不知道哪个接口、哪个用户、什么错、能不能复现。

正面例子(有用日志):

2026-06-23 14:23:01.234 ERROR service=order-api instance=order-7d5b8 trace_id=8f3a4e5b method=POST path=/api/orders user_id=12345 cost_ms=231 error="duplicate key value violates unique constraint orders_no_key" sql="INSERT INTO orders ..."

看到这条日志:

  • 请求已经进入后端(走到了 SQL 执行)
  • 错误是数据库唯一键冲突
  • 排查方向明确:订单号生成逻辑、重复提交、幂等键、约束设计

结构化日志:让日志可查询

结构化日志(Structured Logging) 是把日志写成 JSON 或 key=value 格式,而不是自由文本:

json
{
  "ts": "2026-06-23T14:23:01.234Z",
  "level": "ERROR",
  "service": "order-api",
  "trace_id": "8f3a4e5b",
  "method": "POST",
  "path": "/api/orders",
  "user_id": 12345,
  "cost_ms": 231,
  "error": "duplicate key",
  "error_detail": "..."
}

结构化日志的价值:

  • 可被日志系统(ELK、Loki)按字段索引和查询
  • 可做聚合分析("按 user_id 统计错误次数")
  • 可关联到 trace_id 拉出完整链路

应用层用日志库(zap、logrus、slog、structlog)直接输出 JSON,比正则解析非结构化日志高效得多。

集中日志:多服务的日志聚合

微服务架构下,日志分散在几十上百个 Pod 里,必须集中收集才能高效查询。

主流集中日志方案:

方案组件
ELKElasticsearch + Logstash/Fluentd + Kibana
EFK同上,Logstash 换成 Fluentd
LokiGrafana 出品,基于 Prometheus 思路的日志系统
云厂商阿里 SLS、AWS CloudWatch、腾讯 CLS

采集 Agent 在每台节点跑(K8s 用 DaemonSet),把所有 Pod 的标准输出收到集中存储,运维通过 Kibana/Grafana 查询。

四、链路追踪(Traces)

Trace ID 必须在服务间传递才能串起调用链

为什么需要 Trace

微服务架构下,一次请求穿过多个服务。问题"订单接口慢"可能是:

  • 订单服务自己慢
  • 订单服务调用的用户服务慢
  • 用户服务调用的数据库慢

仅看每个服务的指标和日志,拼不出完整链路——链路追踪就是为解决这个问题。

Trace 与 Span

概念含义
Trace一次完整请求的全链路
Span链路中的一个时间段(如一次 HTTP 调用、一次 SQL 查询)
Trace ID整个 Trace 的唯一标识
Span ID单个 Span 的唯一标识
Parent Span ID当前 Span 的父级,用于构建调用树

一次完整请求的 Trace 形态:

通过 Trace 视图能直接看出:总耗时 3 秒,主要消耗在 INSERT order 的 2.8 秒上——直接定位到数据库这一段,去查慢日志、看索引。

Trace ID 必须在服务间透传

链路追踪能工作的核心机制是 Trace ID 在服务间透传:

入口 → 生成 Trace ID
  ↓ 把 Trace ID 放入 HTTP Header
中间服务 → 从 Header 读取 Trace ID
  ↓ 调用下游时,继续传递 Trace ID
下游服务 → 同样接收并传递

任何一个中间服务没有正确透传 Trace ID,链路就在那里断成两截,后面的耗时归因完全失效。

主流的 Header 命名约定:

  • traceparent(W3C Trace Context 标准,推荐)
  • X-Trace-ID
  • X-B3-TraceId(Zipkin)
  • Uber-Trace-Id(Jaeger)

主流链路追踪方案

方案特点
JaegerCNCF 项目,Go 编写,生态成熟
SkyWalkingApache 项目,中文社区活跃
ZipkinTwitter 开源,历史悠久
OpenTelemetryCNCF 标准,数据采集层,后端可对接 Jaeger/SkyWalking
云厂商阿里 ARMS、AWS X-Ray

OpenTelemetry 正在成为业界标准——它不绑定后端,采集层与展示层解耦,逐步替代旧的 Jaeger SDK、Zipkin SDK 等。

Trace 的采样

每个请求都生成完整 Trace,数据量极大,存储成本高。生产环境通常做采样:

  • 头部采样:入口决定是否记录,常用 1% 采样
  • 尾部采样:先记录,根据请求结果(出错的、慢的)再决定是否保留
  • 业务采样:特定接口、特定用户全采样

合理的采样策略:正常请求低采样率(1%),异常请求(错误、慢)全采样

五、发布链路

发布远不止"把代码放上去"——它是一个多阶段、可追溯的流程

完整发布链路

每个阶段的作用:

阶段作用
Git代码版本管理,每次变更有 commit 记录
CI自动化构建、单元测试、集成测试、安全扫描
镜像/制品不可变的发布制品
制品仓库集中存储,带版本管理与签名
部署系统把制品部署到目标环境,记录发布历史
预发/Staging类生产环境验证
生产正式服务用户
观察发布后监控关键指标

CI/CD 工具

工具特点
Jenkins老牌,插件丰富,灵活
GitLab CI与 GitLab 集成,YAML 配置
GitHub Actions与 GitHub 集成,简单易用
Drone容器化,轻量
Argo CDK8s 原生的 GitOps 工具
TektonK8s 原生 CI/CD 框架

发布记录:可追溯的关键

留下完整的发布记录,经常比代码仓库更快定位问题

线上出问题时的标准排查动作之一:

监控:14:03 错误率开始上升
发布系统:14:01 order-api 从 v1.8.2 滚动更新到 v1.8.3
结论:大概率是这次发布引入,围绕本次变更排查

如果没有发布记录,只能回 git log 翻 commit、问同事"今天谁发了什么"——效率低得多。

发布记录至少要包含

字段内容
谁发的发布人(账号、姓名)
什么时间开始时间、结束时间
发了什么服务名、版本号、commit ID、镜像 tag
发到哪里环境、集群、命名空间
配置变化是否有 ConfigMap/Secret 变更
数据库变更是否有 schema 迁移
变更原因关联的需求/工单
滚动策略灰度比例、放量节奏
关键指标发布期间的错误率、延迟变化

六、灰度发布

为什么要灰度

新版本直接全量上线,如果有问题,全部用户立刻受影响。灰度发布让新版本先接收一部分流量,问题影响范围可控:

  • 出问题只影响少量用户
  • 验证通过后再扩大范围
  • 出现严重问题立刻回滚,损失可控

灰度方式

方式实现适用场景
按比例5% → 20% → 50% → 100% 逐步放量最常见,任何场景
按用户内部账号、测试账号、特定客户先用需要识别用户的场景
按地域某个区域先切新版本多机房架构
按设备/客户端某版本 App、某型号设备先用移动端发布
按实例少量实例替换为新版本,观察K8s 滚动更新的天然机制
按时间工作时间小流量、夜间扩大配合业务低峰

灰度观察:不能只看"服务还活着"

灰度期间的观察,必须对比新旧版本的关键指标:

  • 错误率(新版本 vs 旧版本)
  • P95/P99 延迟
  • 关键业务指标(下单成功率、支付成功率)
  • 日志中的新增错误类型
  • 数据库慢查询
  • 告警变化

某些问题在小流量下根本看不出来,放量到 50% 才暴露——所以**每放一档,都要观察足够长时间(通常 10-30 分钟,关键业务更长)**再放下一档。

渐进式发布的几种工具

工具特点
Flagger基于 K8s + 服务网格的自动化金丝雀
Argo RolloutsK8s 原生的渐进式发布工具
Istio / Linkerd服务网格自带流量切分
SpinnakerNetflix 开源的多云部署平台

这些工具支持自动观察指标、自动决策放量或回滚。

七、回滚

回滚不是万能药

代码可以快速回滚,但数据库结构和数据不一定能回滚

典型场景:新版本给订单表加了 coupon_id 字段,业务代码写入了这个字段。回滚到旧版本后:

  • 旧代码不认识 coupon_id 字段,可能报错或丢弃
  • 已经写入的数据怎么处理?
  • 用户已经看到了新功能,回滚后这些数据消失?

数据库变更的兼容性原则

涉及数据库变更的发布必须分步进行,确保新旧版本能兼容:

阶段 1:发布 v1.8(兼容版本)
  - 数据库新增 coupon_id 字段(允许 NULL)
  - 代码可以读 coupon_id 但不写
  - 旧版本 v1.7 也能正常工作

阶段 2:发布 v1.9(使用新字段)
  - 代码开始写入 coupon_id
  - 数据库结构不变(已经在 v1.8 准备好)

阶段 3:发布 v2.0(清理旧逻辑)
  - 代码不再处理 coupon_id 缺失的情况
  - 数据库可以设 NOT NULL

这套"先兼容再使用"的发布模式被称为 Expand-Contract 模式蓝绿数据库变更

一份典型的回滚记录

14:01 order-api v1.8.3 开始灰度 10%
14:06 指标显示 5xx 比例从 0.2% 升到 6.8%
       集中在 POST /api/orders,错误类型:duplicate key
14:08 决策:停止放量,流量切回 v1.8.2
14:09 灰度比例降为 0%,旧版本 v1.8.2 承载 100% 流量
14:12 错误率回落到 0.3%
14:15 确认恢复,启动复盘流程
原因:v1.8.3 引入的并发场景下订单号生成冲突
后续:开发修复后,15:30 v1.8.4 重新灰度

这种记录能直接对应监控曲线、入口日志、应用日志——复盘时能精确还原每个时间点的决策

发布管理看起来繁琐,但真出事时,有这套流程和记录的团队与没有的团队,处理速度能差一个数量级

八、可观测性 + 发布:配合是关键

可观测性和发布管理是相互配合的:

配合点价值
发布前定义观察指标知道发布期间该看什么
发布期间监控自动比对自动识别新旧版本的差异
自动化金丝雀监控指标触发自动放量或回滚
发布记录关联监控曲线故障复盘可精确还原
链路追踪定位新版本异常跨服务问题快速定位到引入版本

完整的现代发布工作流是:

  1. CI 构建测试 → 通过
  2. 部署到 staging,自动化测试 → 通过
  3. 灰度 10%,观察 15 分钟,关键指标稳定 → 自动放量
  4. 灰度 50%,观察 15 分钟 → 自动放量
  5. 100%,观察 30 分钟 → 发布完成
  6. 任意阶段指标异常 → 自动回滚 + 告警

这一切的基础是完善的可观测性——没有指标监控、没有日志、没有 Trace,自动化决策无从谈起,只能靠人工观察+手工操作,效率低且容易错过问题。

可观测性和发布管理,共同构成"现代运维的双支柱"。