元数据卡
- 前置知识:第14章(微服务基础模式)
- 预计时间:40 分钟
- 核心难度:进阶
- 完成标志:理解常见部署模式和发布策略
你的进度
你的引擎们都跑起来了,数据一致了。但你每次发布新版本都要停掉整条流水线 30 秒——工坊的体验是“任务系统维护中”。而且有一次你错配了一根管道参数,整条产线炸了半天。
你不敢随便升级,但工坊主想一天迭代 20 次。
你需要一种不停机就能换零件的技术。 你的任务
代码写好了、测试过了、镜像构建了——最后一步是最危险的:把新代码送进生产环境。用错策略会导致客户端错误、数据丢失、或者最糟的——回滚困难。这章讲你需要的部署与运维模式:Sidecar 做服务辅助进程、蓝绿/金丝雀发布让切换无损、Feature Flag 让功能开关随心控制、Ambassador 做通信代理。
本章分层
- 必读:蓝绿部署、金丝雀发布、Feature Flag
- 选读:Sidecar、Ambassador
- 进阶:混沌工程入门
本章不会要求你掌握
- Kubernetes Operator 开发
- 完整的可观测性体系
破局 · 溯源
你的服务晚上 10 点上线新版本。部署完成后,玩家积分全部变成 0。回滚花了 8 分钟——这 8 分钟里用户看到了积分清零、报错页面、然后恢复原状。客服电话被 30 个玩家打爆。
原因:新版本的积分计算脚本里 score * 0.5 写成了 (int) (score / 0.5)——测试没覆盖到。如果能以 1% 的流量先试跑一下,发现有问题就停下——8 分钟的回滚窗口可以缩短到 30 秒。
第一层:蓝绿部署——两个环境,一键切换
蓝绿部署维护两套完全相同的环境:
蓝环境 (Blue): 目前运行 v1.0(生产流量)
绿环境 (Green): 新部署 v2.0(无流量)
切换时:负载均衡器从蓝切到绿
↓
绿环境 (Green): 现在运行 v2.0(生产流量)
蓝环境 (Blue): 保持 v1.0(备用)# Kubernetes 蓝绿切换示例
# 两个 deployment 共存
apiVersion: apps/v1
kind: Deployment
metadata:
name: tournament-api-blue
labels:
app: tournament-api
version: blue
spec:
replicas: 3
template:
metadata:
labels:
app: tournament-api
version: blue
# Service 通过 label selector 控制流量
apiVersion: v1
kind: Service
metadata:
name: tournament-api
spec:
selector:
app: tournament-api
version: blue # 切换到 green 时只改这里切换命令(假设你用 Nginx 做负载均衡器):
# 切到绿环境
sed -i 's/server green.*/server green:8080 weight=1;/' nginx.conf
nginx -s reload
# 验证 5 分钟后没发现问题,可以下线蓝环境
# 如果发现问题:切回蓝环境就是一条命令的事
sed -i 's/server green.*//' nginx.conf
nginx -s reload蓝绿的最大优点是瞬时回滚——切回蓝环境只需几十毫秒。缺点是资源加倍(两套环境运行)。对于中小型服务,蓝绿是推荐的发布策略。
第二层:金丝雀发布——百分之一流量先试
金丝雀发布不直接切 100% 流量——先给新版本 1% 的流量,观察几分钟没问题,再逐步提升到 5%、20%、100%。
v1.0 (95% 流量)
v2.0 (5% 流量,金丝雀)
↓ 监控无问题
v1.0 (70% 流量)
v2.0 (30% 流量)
↓ 逐步提升
v2.0 (100% 流量)# Kubernetes 金丝雀发布
apiVersion: apps/v1
kind: Deployment
metadata:
name: tournament-api-canary
spec:
replicas: 1 # 只启动一个金丝雀实例
---
apiVersion: v1
kind: Service
metadata:
name: tournament-api
spec:
# Service 通过版本标签分配流量
# v1.0 有 19 个实例,v2.0 有 1 个实例 → 5% 左右的流量到 v2.0在 Spring Cloud Gateway 中可以通过权重路由实现:
spring:
cloud:
gateway:
routes:
- id: tournament-api-v1
uri: lb://tournament-api-v1
predicates:
- Weight=api-group, 95
- id: tournament-api-v2
uri: lb://tournament-api-v2
predicates:
- Weight=api-group, 5金丝雀的精髓不只是流量分配,还有监控对比——你需要对比新版本的错误率、延迟、资源消耗和旧版本比,有没有显著差异。只要金丝雀的错误率高了 0.5%,自动终止发布。
# 金丝雀退出条件示例(伪代码)
if (canary.error_rate > baseline.error_rate * 1.1) {
canary.rollback();
}第三层:Feature Flag——功能开关随心控制
Feature Flag(功能开关、特性门禁)是一种技术:用条件判断控制某个功能是否启用,而不是通过部署代码。
// ch16/featureflag/FeatureFlagService.java
@Service
public class FeatureFlagService {
private final Map<String, Boolean> flags = new ConcurrentHashMap<>();
public boolean isEnabled(String flagName) {
return flags.getOrDefault(flagName, false);
}
public void setEnabled(String flagName, boolean enabled) {
flags.put(flagName, enabled);
}
}
// 使用 Feature Flag
public class MatchService {
private final FeatureFlagService featureFlags;
public int calculateScore(Match match) {
if (featureFlags.isEnabled("new-scoring-algorithm")) {
return newAlgorithm(match); // 新算法
}
return oldAlgorithm(match); // 旧算法
}
}Feature Flag 的典型用途:
- 渐进式发布——新功能先对内部员工开启,再过几天 10% 用户,最后全量。
- A/B 测试——随机分配用户到 A 组或 B 组,对比指标。
- 立即回滚——爆了?切掉 flag 不用重新部署。
- 暗部署——代码已经部署了,但功能关着——上线时远程开启。
// 基于用户的 Feature Flag
public boolean isFeatureEnabled(String userId, String flagName) {
// 按用户 ID hash,保证同一个用户始终看到同一个版本
int bucket = Math.abs(userId.hashCode()) % 100;
int threshold = featureConfig.getPercentage(flagName); // 10
return bucket < threshold; // 前 10% 的用户看到新功能
}Feature Flag 的陷阱:flag 积累——半年后代码里有 30 个 if (isEnabled("xxx")),没人记得这些 flag 是做什么用的。所有 flag 必须有生命周期——开启后追踪一段时间,确认没问题就清理 flag 代码。
第四层:Sidecar 与 Ambassador——辅助进程模式
Sidecar(边车模式): 在你的主服务旁边运行一个辅助进程,它们共享同一个网络命名空间。日志收集、监控代理、服务代理都用 Sidecar 实现,不需要侵入主服务的代码。
+-------------------------+
| Pod |
| +---------------------+ |
| | 主服务 (API) | |
| +---------------------+ |
| +---------------------+ |
| | Sidecar (日志收集) | | ← 不修改主服务代码
| +---------------------+ |
+-------------------------+# Kubernetes Sidecar 示例
apiVersion: v1
kind: Pod
metadata:
name: tournament-api
spec:
containers:
- name: api
image: tournament-api:1.0
- name: log-collector # Sidecar
image: fluentd:latest
volumeMounts:
- name: logs
mountPath: /var/log/appAmbassador(大使模式): 一个专门做网络通信代理的 Sidecar。负责处理重试、超时、断路器等网络逻辑——主服务不需要知道网络细节。
主服务 → localhost:9999 → Ambassador(代理) → 外部服务
├── 重试逻辑
├── 超时设置
└── 断路器// 主服务代码——不知道外部服务的网络细节
public class TournamentService {
private final HttpClient httpClient;
public TournamentService() {
// 指向本地的 Ambassador 代理
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(1))
.build();
}
public int getExternalScore(String playerId) {
// 访问 localhost:9999——Ambassador 负责转发到真正的外部服务
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:9999/scores/" + playerId))
.build();
// Ambassador 处理了重试、熔断等——主服务代码干净
}
}Envoy 和 Istio 的 Sidecar 代理就是 Ambassador 模式在生产中的具体实现。
常见陷阱
陷阱一:蓝绿部署的资源成本。 两套环境就是双倍的服务器费用(或 Kubernetes 节点)。对于小服务,可以只用灰度 + 回滚脚本——不需要完整的两套环境。
陷阱二:金丝雀排量太随意。 "先 5%,然后 100%"——没有中间的逐步提升和监控对比。金丝雀发布必须有自动化的"成功/失败"判断——人工盯着 Grafana 等 10 分钟太容易走神。
陷阱三:Feature Flag 的过时代码。 "这个 flag 三个月前开的——但我忘了删掉旧分支。" 需要定期清理 flag:根据 flag 的创建日期、状态、命中率来判断是否该删。
陷阱四:没有瞬时回滚能力。 金丝雀 100% 以后你把旧版本实例全杀掉了。然后出了问题——你只能重新构建、重新部署旧版本。金丝雀到 100% 后仍然保留至少一个旧版本的副本 30 分钟。
通关挑战
- 热身:在你的项目中引入一个 Feature Flag(不需要框架,一个
Map<String, Boolean>就行)。把你最近写的一个功能包在 flag 里。 - 挑战:设计你的服务的发布流程——手动或自动画一个流程图:从
git tag到生产环境,经过哪些环境、用什么发布策略、回滚方案是什么。 - 观察:调研一个你用的 SaaS 产品(GitHub、Slack),看它是如何做灰度发布的——通常它们会在 Changelog 或 Status page 里提到"逐步推出"。
旅人笔记
部署模式保证了"代码变更"这个风险最高的操作可以被控制——蓝绿让你瞬时切换,金丝雀让你带着刹车前行,Feature Flag 让你不部署也能控制功能开关,Sidecar 与 Ambassador 把基础设施能力放进服务旁边。
下一站预告
部署策略解决的是"怎么把代码安全地送出去"。但你的系统运行起来后,各个服务之间是怎么通信的?下两章深入通信模式:事件驱动架构和 AI 系统模式。