微服务(三):可观测性、发布演进与落地实践
微服务(三):可观测性、发布演进与落地实践
导语:前两篇解决"怎么拆"和"怎么稳",本篇解决"怎么看得见、怎么发得安全、怎么迁得过去"。核心 6 题:可观测性三支柱与 TraceId 透传、蓝绿/滚动/金丝雀与全链路灰度、服务网格的价值与代价、绞杀者模式迁移单体、容量规划与全链路压测,最后用"微服务反模式清单"压轴,共 20 题收官。
一、可观测性
15. 微服务为什么必须有可观测性?Logging / Metrics / Tracing 三支柱怎么配合?
答: 单体时代"登机器看日志"就够了;微服务下一个请求横跨十几个服务、几十个实例,必须靠体系化的可观测能力才能定位问题。
可观测性 vs 监控:
- 监控(Monitoring) 回答"是否出问题了"——基于预设的仪表盘和告警(CPU、QPS、错误率)。
- 可观测性(Observability) 回答"为什么出问题"——能基于系统输出的信号,去解释从未预设过的问题(微服务绝大多数故障都是"没见过的")。
三支柱:
| 支柱 | 数据形态 | 典型工具 | 成本 | 擅长 |
|---|---|---|---|---|
| Metrics | 时序数值,可聚合 | Prometheus + Grafana | 低(预聚合) | 趋势、告警、容量 |
| Tracing | 调用链 Span 树 | SkyWalking、Jaeger、Zipkin | 中(需采样) | 定位瓶颈与错误点 |
| Logging | 原始文本事件 | ELK、Loki | 高(全量存储贵) | 根因细节、审计 |
TraceId 全链路透传是怎么做的:
四个必过的透传卡点(漏一个链路就断):HTTP Header → RPC 上下文 → MQ 消息属性 → 线程池异步/定时任务。其中第 4 点最容易漏,因为 ThreadLocal 不会跨线程,需要用 TTL(TransmittableThreadLocal)之类的方案显式传递。
采样策略:全量采集成本高,通常用头部采样(在入口决定,简单但可能漏掉偶发错误)+ 尾部采样(链路结束后按错误/慢请求决定保留,更准但成本高)。生产常见做法:错误与慢请求 100% 保留,正常请求按 1%~10% 采样。
标准排障路径:Metrics 告警发现异常 → Tracing 找到慢/错的服务 → 用 TraceId 拉出该请求的全部 Logging 明细 → 定位根因。
答题加分点:追问"三支柱之外还缺什么?"——答:Profling(持续性能剖析) 常被称为第四支柱,用来解释"为什么这段代码慢"(CPU/内存火焰图),而 Tracing 只能告诉你"哪个服务慢"。
二、发布与演进
16. 微服务的发布策略有哪些?蓝绿、滚动、金丝雀怎么选?全链路灰度怎么做?
答:
| 策略 | 做法 | 资源成本 | 回滚速度 | 风险 | 适用 |
|---|---|---|---|---|---|
| 滚动发布 | 逐批替换实例 | 低(无需冗余) | 慢(要重新滚动) | 中(期间新旧并存) | 日常默认,K8s 原生 |
| 蓝绿部署 | 两套全量环境,整体切换 | 高(需 2 倍资源) | 秒级(切流量即可) | 低(切换前可完整验证) | 关键系统、大版本 |
| 金丝雀/灰度 | 小比例真实流量验证后逐步放量 | 低 | 快(把比例调回 0) | 最低(爆炸半径可控) | 高频发布、互联网主流 |
全链路灰度(泳道):金丝雀的难点在于——只在网关分流 5% 是不够的,因为请求会沿调用链扩散,如果服务 B 只有 v1,灰度请求最终又回到旧逻辑。全链路灰度要求流量标签在整个调用链上传递。
实现要点:
- 流量染色:在网关按用户 ID、地域、设备、白名单等规则打上
gray=true标签。 - 标签透传:HTTP Header、RPC attachment、MQ 消息属性全链路传递。
- 标签路由:各服务的负载均衡优先选择同标签实例,没有则回落基线环境(保证链路不断)。
- 数据隔离:灰度产生的写数据要能区分(影子库/影子表或业务标记),避免污染正式数据。
发布与数据库变更必须解耦——这是灰度能成立的前提。采用扩展-收缩模式(Expand-Contract):
关键结论:"服务可以秒级回滚,数据库不能"。所以凡是涉及表结构变更的发布,必须保证新旧版本代码能同时兼容同一份数据结构,否则灰度时就必然出现"老代码读到新结构"的报错。
17. 什么是服务网格(Service Mesh)?Sidecar 解决了什么,代价是什么?
答:
背景:微服务治理能力(注册发现、熔断、限流、重试、mTLS、可观测)最初都以 SDK 形式嵌在业务代码里(Spring Cloud、Dubbo)。这带来三个问题:
架构(以 Istio 为例):
- 数据面:每个 Pod 旁挂一个 Sidecar 代理(Envoy),拦截进出流量,实现路由、重试、熔断、mTLS、遥测上报。
- 控制面:集中下发策略与证书,业务完全无感知。
收益 vs 代价:
| 收益 | 代价 |
|---|---|
| 业务零侵入:治理能力升级不用改业务代码 | 延迟增加:每跳多两次代理转发,通常 +1~5ms |
| 多语言统一:Java/Go/Python 同一套治理能力 | 资源开销:每个 Pod 多一个容器,CPU/内存与连接数上升 |
| 能力集中演进:重试、mTLS、灰度一次升级全站生效 | 复杂度上移:多了一层基础设施要运维、要排障 |
| 安全默认化:零信任 mTLS 开箱即用 | 排障链路变长:问题可能在业务、Sidecar、控制面三者之一 |
| 统一遥测:无需各语言埋点 | 学习曲线陡:xDS、CRD 概念多 |
什么时候该上 Service Mesh:
- 服务数量大(几十上百)且多语言栈并存
- 有强合规/安全要求(全链路 mTLS、细粒度授权)
- 有专职基础设施团队
什么时候别上:服务数量不多、单一 Java 栈、团队没有专职运维——此时 Spring Cloud Alibaba / Sentinel 这类 SDK 方案性价比更高。
行业趋势:Sidecar 的资源与延迟开销催生了 Ambient Mesh(无 Sidecar,按节点共享代理) 与 eBPF 方向的探索;但面试中答经典 Sidecar 架构 + 上述取舍即可。
三、落地实践
18. 如何把单体平滑迁移到微服务?绞杀者模式怎么落地?
答: 绞杀者模式(Strangler Fig Pattern,Martin Fowler) 是单体迁移的主流方法:新功能在单体之外新建服务实现,用一层代理/门面把流量逐步从单体引流到新服务,直到单体被"绞杀"消亡。
落地步骤:
- 建代理层:在单体前面加一层网关/反向代理,此时全部流量仍走单体——这一步不改任何业务逻辑,风险最低。
- 识别边界:按第 3 题的 DDD 方法,挑出变化最快、伸缩需求最特殊、依赖最少的模块先动手。
- 建防腐层(ACL):新服务需要旧数据时,通过一个防腐层访问单体(或读从库/CDC 同步),避免新服务直接依赖单体的内部数据模型,否则旧模型会被永久固化。
- 先拆代码,后拆数据:先让新服务独立部署并暂时共用单体的库(可读从库或经由 ACL),验证稳定后再做数据迁移——同时拆代码和数据 = 大爆炸,风险不可控。
- 数据迁移:双写 → 校验 → 切读 → 停双写 → 清理(配合影子比对确保一致)。
- 灰度引流:用第 16 题的金丝雀策略逐步把流量切到新服务,随时可回切。
- 收尾:确认无流量后下线模块,最后下线代理层或将其转为正式网关。
必须避免的反模式:大爆炸重写(Big Bang Rewrite)——停下新需求、全量重写、一次性切换。历史上几乎都以失败告终:周期长、需求在重写期间继续变化、切换时无法回滚。
答题加分点:追问"迁移过程中单体和新服务的数据不一致怎么办?"——答:以单体为过渡期的权威数据源,新服务侧做定时比对/对账并告警;迁移完成前不切换写入权威。
19. 微服务的容量怎么规划?全链路压测怎么做?
答:
容量规划的基本顺序:
- 单机容量:以目标 RT 与错误率为前提测出的 QPS 拐点,而不是"压到崩"的极限值。
- 冗余系数:线上不能跑到拐点,通常按 70% 水位作为容量上限,留出突发余量。
- 木桶效应:链路容量 = 各依赖服务容量的最小值,要注意下游容量不足时扩容上游毫无意义。
压测的三个层次:
全链路压测的四个关键点:
| 关键点 | 说明 | 常见事故 |
|---|---|---|
| 流量染色 | 压测流量带标记,全链路透传,各环节据此路由到影子资源 | 压测流量被当成真实流量处理 |
| 数据隔离 | 写入影子库/影子表(同库不同表,或独立库),MQ 用独立 topic | 污染生产数据,产生假订单、假账单 |
| 依赖隔离 | 外部第三方、短信、支付、物流必须走 Mock/挡板 | 真发短信给真实用户、真扣款 |
| 可中断 | 一键停压 + 指标实时守护(DB 负载、错误率超阈值自动停止) | 压测把生产压垮 |
压测产出:各服务的容量水位与瓶颈点、限流阈值的设定依据、扩容预案(扩容哪个服务、扩多少、耗时多久),以及降级预案的验证。
关键认知:全链路压测的最大难点不是发压,而是数据隔离。没有隔离方案就不要在生产做全链路压测——宁可在独立压测环境做单链路压测 + 容量推算。
20. 微服务架构的典型反模式有哪些?如何判断团队是不是在做"分布式单体"?
答: 这是微服务方向的常见压轴题,考察真实落地经验。
反模式清单:
| 反模式 | 表现 | 危害 | 正确做法 |
|---|---|---|---|
| 分布式单体 | 服务拆了,但必须一起发布、共享库 | 付出了分布式成本,没得到自治收益 | 按第 4 题三条标准重划边界 |
| 共享数据库 | 多服务读写同一库表 | 数据耦合,无法独立演进 | Database per Service + 事件同步 |
| 纳米服务 | 一个服务只干一件事,调用链 8 跳以上 | 延迟、排障、一致性成本爆炸 | 合并过细服务 |
| 上帝服务 | 一个 Common/Base 服务被所有人依赖和改动 | 成为全站瓶颈与发布阻塞点 | 下沉为只增不改的库,或按领域拆分 |
| 循环依赖 | A 调 B,B 又调 A | 无法独立部署,启动顺序死锁 | 抽取公共逻辑到下游,或改用事件解耦 |
| 同步长链 | A→B→C→D 串行同步调用 | 可用性相乘衰减,延迟累加 | 并行调用、异步化、CQRS 读模型 |
| 无脑重试 | 任何错误都重试,无退避无预算 | 重试风暴,把下游彻底打垮 | 指数退避 + 抖动 + 重试预算 |
| 忽略超时 | 调用依赖不设超时 | 连接池耗尽 → 雪崩 | 每个依赖必配超时,逐层收敛 |
| 网关写业务 | 网关里塞下单校验、促销计算 | 网关变成新瓶颈 | 网关只做横切,业务下沉 BFF |
| 无契约测试 | 提供者改接口,消费者上线才知道 | 跨团队联调事故 | 契约测试 + 向后兼容 + 灰度 |
| 没有可观测性就上线 | 只有单机日志,出事靠猜 | 故障定位以小时计 | 先建三支柱再扩容服务数 |
"是不是在做分布式单体"的现场诊断清单:
命中 2 条以上,基本可以判定为分布式单体。
正确的纠偏路径:
一句话总结:微服务的成功标志不是"服务数量",而是"能否由一个团队独立、安全、频繁地发布自己的服务"。达不到这一点,拆得再细也只是把复杂度从代码搬到了网络。
