Skip to content

观测和发布

系统跑起来之后,日常运维最怕两类空白:出问题的时候不知道发生了什么,发布新版本的时候不知道影响到了哪里。这两件事对应的就是观测性和发布管理——日志、指标、链路追踪、事件、告警,这些用来让系统状态可见;CI/CD、镜像、灰度、回滚,这些用来让代码和配置变更可控。

一、可观察信号

进程存在不等于服务正常。可能用户已经打不开页面了,但你的进程还在那儿好好跑着;接口 P95 延迟可能已经飙到几秒了,但服务状态显示"running";数据库连接池可能快被打满了,但 CPU 使用率看起来还正常。所以光看"进程在不在"远远不够,要从多个维度的信号里看系统真实状态。

主流的可观察信号有四类:

信号记录什么适合看什么
Metrics(指标)随时间变化的数值QPS、延迟、错误率、CPU、内存这种聚合数据
Logs(日志)一条条事件记录错误堆栈、请求详情、业务状态变化
Traces(链路追踪)一次请求的完整调用路径跨服务耗时、慢在哪一段
Events(事件)系统状态变化Pod 重启、调度失败、告警触发、发布记录

这四类信号各有所长,实际排查问题时通常是配合着用。举个例子,接口突然慢了,排查流程大概是这样:指标里先看到 P95 延迟抬升 → 日志里找到同一时间的慢请求和错误堆栈 → Trace 里看到时间主要耗在订单服务到数据库这一段 → 数据库慢日志里看到同一条 SQL 的 Query_timeRows_examined 都很高。这样才算把线索串起来,定位到根因。

二、指标

指标最大的好处是能把系统状态量化成数值,适合回答"是不是变差了""从什么时候开始变差""影响范围有多大"这种问题。服务层最常看的几个指标:

指标看什么
请求量(QPS)流量是不是突然变化(被刷、被爬、被攻击)
错误率5xx 比例、业务失败码比例是不是上涨
延迟(Latency)P95、P99 有没有抬升
饱和度(Saturation)CPU、内存、连接池、线程池是不是接近上限

这里有个特别重要的点:别只看平均值。延迟这个指标尤其坑——平均延迟 80ms 看起来很美好,但 P99 可能已经飙到 3 秒了,也就是说每 100 个用户里有 1 个等了 3 秒,体感非常差。平均值的"长尾"被均值拉平之后,慢请求的痛感完全被掩盖了,所以 P95、P99 这种尾部指标才重要。

告警设计也有讲究——不能只盯着机器 CPU 这种基础资源。一个业务接口 500 比例升到 10%,哪怕机器资源还很空,也已经严重影响用户了,必须立刻告警。反过来,CPU 偶尔飙升一下但错误率和延迟都没变化,优先级反而没那么高,可能是某个统计任务在跑。告警要盯着"用户感知的痛",不是盯着"机器忙不忙"

三、日志

日志记录的是具体发生过的事件。入口日志里能看到客户端 IP、Host、路径、状态码、upstream 地址、耗时;应用日志里能看到请求参数摘要、用户 ID、trace id、错误类型和堆栈。

一条有用的接口错误日志至少要能回答几个核心问题:

字段干什么
trace_id把入口、网关、后端、下游服务的日志串起来
method / path定位是哪个接口
statusHTTP 状态码或业务状态
cost_ms请求耗时
error错误类型或异常信息
user_id / tenant_id判断影响范围

只写一句 error occurred 的日志基本没法排查。一条有用的日志应该长这样:

text
trace_id=8f3a path=/api/orders method=POST status=500 cost_ms=231 error="duplicate key value violates unique constraint orders_no_key"

看到这条记录,排查方向就很明确:请求已经进了后端,错误来自数据库唯一键冲突。下一步去看订单号生成逻辑、是不是有重复提交、幂等键有没有生效、数据库约束设计是不是合理。如果日志只有 "error occurred",这几个方向都没法判断,只能靠猜。

四、链路追踪

微服务拆开之后,一次请求会穿过好几个服务。订单接口慢,可能是订单服务自己慢,也可能是它调的用户服务慢,也可能是用户服务调的数据库慢。光看每个服务的指标和日志,根本拼不出完整链路——这就是链路追踪(Trace)要解决的问题。

链路追踪用 Trace 表示整次请求,用 Span 表示其中一段调用。一次下单请求大概会形成这样的链路:

下单接口 3 秒才返回,Trace 能告诉你这 3 秒花在哪里:网关 20ms、订单服务内部 100ms、用户服务 30ms、数据库查询 2800ms——慢点就在数据库查询附近,直接去看慢日志和索引。没有 Trace 的话,你要在好几个服务的日志之间靠时间戳硬拼,效率低得让人崩溃。

链路追踪依赖 trace id 在服务之间透传。入口生成 trace id 之后,后续所有调用(HTTP、gRPC、消息)都要带着它,下游服务继续往下传。某个中间服务没有透传,链路就在那里断成两截,后面的耗时归因就完全失效。所以微服务框架基本都内置了 trace id 透传的中间件,但要确认所有出口调用都接上了。

五、发布链路

发布远不是"把代码放上去"这么简单。一次完整的发布要经过好几个阶段:

每个阶段都可能出问题,但最关键的是留下完整的发布记录。线上出问题的时候,发布记录经常比代码仓库更快定位到时间点。比如监控显示 14:03 开始错误率上涨,翻发布系统看到 14:01 order-api:v1.8.3 开始替换实例,那基本就能锁定是这次发布引入的问题,直接围绕这次变更排查。如果没有发布记录,你得回 git log 翻 commit、问同事"今天谁发了什么",效率低得多。

发布记录里至少要能查到这些:谁发的、发了哪个 commit 或镜像 tag、影响哪个环境和哪些服务、配置有没有变化、什么时候开始、什么时候结束

六、灰度与回滚

灰度发布让新版本先接一部分流量,出问题影响范围可控。具体怎么灰度,看业务特性:

灰度方式适合什么场景
按比例5%、20%、50%、100% 逐步放量,最常见
按用户内部账号、测试账号、指定客户先用
按地域某个区域先切新版本(适合多机房架构)
按实例少量实例先替换,观察一段时间再继续

观察灰度的时候,不能只看"服务还活着"。错误率、P95/P99 延迟、关键业务指标、日志异常、告警、数据库慢查询,都要跟旧版本对比。有些问题在小流量下根本看不出来,放量到 50% 才暴露——所以每放一档,都要观察足够长时间(通常 10-30 分钟)再放下一档。

回滚也不是万能药。代码可以回滚,但数据库结构和数据不一定能回滚。如果新版本已经写入了新字段、新状态、新格式的消息,回滚到旧版本之后,旧版本能不能正确处理这些新数据?这件事发布前就要想清楚。比如新版本给订单表加了 coupon_id 字段,旧版本代码读到这个未知字段可能会报错或者直接丢弃。所以涉及数据库结构变更的发布,通常要分两步:先发兼容版本(旧代码能读新结构),再发真正用上新结构的版本。

一条比较完整的发布记录会写到这种程度:

text
14:01 order-api v1.8.3 开始灰度 10%
14:06 5xx 从 0.2% 升到 6.8%,集中在 POST /api/orders
14:08 停止放量,流量切回 v1.8.2
14:12 错误率恢复到 0.3%

这种记录能直接对应监控曲线、入口日志、应用日志——出问题复盘的时候,能精确还原每个时间点发生了什么决策。发布管理看起来繁琐,但真出事的时候,有这套流程和记录的团队跟没有的团队,处理速度能差一个数量级。