22.12-灰度发布
要点
- 灰度发布(Canary Release)是把新版本逐步推给一小部分用户,观察没问题后再全量发布
- 目的是降低风险——新版本有 bug 也只影响少数用户
- Workers 支持通过权重分配流量实现灰度
- Vercel 支持通过 Preview Deployments 做灰度验证
内容
1. 什么是灰度发布
传统的部署是「一刀切」——新版本部署后,所有用户立即使用新版本。如果新版本有 bug,所有用户都受影响。
灰度发布的思路不同:
10% 用户 → 新版本(v2)
90% 用户 → 旧版本(v1)
观察一段时间后:
50% → v2
50% → v1
再观察:
100% → v2
0% → v1
如果在灰度阶段发现问题,可以立即停止灰度并回滚,影响范围很小。
2. 灰度发布的策略
常见的灰度策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 按比例 | 随机分配一定比例的用户 | 通用 |
| 按地域 | 先在某个地区发布 | 地域性服务 |
| 按用户组 | 内部员工先试用 | 功能验证 |
| 按请求头 | 带特定 header 的请求走新版本 | 测试 |
3. Cloudflare Workers 的灰度
Workers 可以通过权重分配流量:
// wrangler.jsonc
{
"routes": [
{ "pattern": "api.example.com", "zone_name": "example.com" }
]
}在 Cloudflare Dashboard 里:
Workers & Pages → 你的 Worker → Traffic → 可以配置多个版本的流量权重
但 Workers 原生的灰度能力有限。更灵活的方式是用 Cloudflare 的 Rules 或 Workers 代码自己实现。
3.1 用代码实现灰度
// src/index.ts
export default {
async fetch(request: Request, env: Env) {
const userId = request.headers.get('X-User-Id')
// 10% 的用户走新版本
if (shouldUseNewVersion(userId)) {
return newApp.fetch(request, env)
}
return oldApp.fetch(request, env)
},
}
function shouldUseNewVersion(userId: string | null): boolean {
if (!userId) return false
// 用 hash 保证同一用户总是走到同一版本
const hash = hashCode(userId)
return hash % 100 < 10 // 10% 的用户
}
function hashCode(str: string): number {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i)
hash |= 0
}
return Math.abs(hash)
}4. Vercel 的灰度
Vercel 的灰度通过 Preview Deployments 和 Edge Config 实现:
4.1 Preview Deployments
每个 PR 自动部署一个预览版本,可以让特定用户先测试:
PR #123 → 预览版本 https://xxx.vercel.app
让内部团队或测试用户先访问预览版本,确认没问题后再合并到 main。
4.2 Edge Config
Vercel 的 Edge Config 是一个全局配置存储,可以用来做 feature flag:
// app/api/feature/route.ts
import { get } from '@vercel/edge-config'
export async function GET() {
const newFeatureEnabled = await get('new_feature_enabled')
if (newFeatureEnabled) {
return newFeature()
}
return oldFeature()
}在 Vercel Dashboard 里修改 Edge Config,可以即时切换 feature flag,不需要重新部署。
5. Feature Flags
Feature flag 是灰度发布的一种简化形式——用开关控制新功能是否启用:
// src/lib/feature-flags.ts
export const FLAGS = {
NEW_CHAT_UI: 'new_chat_ui',
AI_MODEL_V2: 'ai_model_v2',
DARK_MODE: 'dark_mode',
}
export function isFeatureEnabled(flag: string, userId?: string): boolean {
// 简单实现:按用户 ID hash 分桶
if (!userId) return false
const hash = hashCode(userId)
switch (flag) {
case FLAGS.NEW_CHAT_UI:
return hash % 100 < 20 // 20% 的用户
case FLAGS.AI_MODEL_V2:
return hash % 100 < 50 // 50% 的用户
default:
return false
}
}// src/routes/chat.ts
app.post('/chat', async (c) => {
const userId = c.get('userId')
if (isFeatureEnabled(FLAGS.AI_MODEL_V2, userId)) {
return callNewModel(c)
}
return callOldModel(c)
})6. 灰度发布的监控
灰度期间需要重点监控:
- 错误率:新版本的错误率是否高于旧版本
- 延迟:新版本的响应时间是否正常
- 业务指标:转化率、用户活跃度等是否受影响
// 对比新旧版本的指标
app.use('*', async (c, next) => {
const startedAt = Date.now()
const version = c.get('version') // 'v1' or 'v2'
await next()
const duration = Date.now() - startedAt
// 记录指标
log.info('http.request', {
version,
status: c.res.status,
duration,
path: c.req.path,
})
})7. 灰度发布的流程
一个典型的灰度发布流程:
1. 代码合并到 main,但不全量部署
2. 部署到 staging 环境,跑自动化测试
3. 部署到 production,但只分配 5% 流量
4. 观察 1 小时,检查错误率和业务指标
5. 没问题 → 增加到 20%
6. 观察 4 小时
7. 没问题 → 增加到 50%
8. 观察 1 天
9. 没问题 → 全量发布 100%
如果任何阶段发现问题:
立即停止灰度,回滚到旧版本
分析问题,修复后重新走灰度流程
8. 适用场景
灰度发布适合:
- 重大功能变更:UI 改版、架构升级
- 高风险改动:数据库迁移、支付逻辑变更
- AI 模型切换:新模型的效果需要验证
- 性能优化:需要对比新旧版本的性能数据
不需要灰度的场景:
- 小修复:文案修改、bug fix
- 向后兼容的改动:加字段、加接口
- 内部工具:用户量小,影响可控
9. 小结
灰度发布的核心:
- 逐步放量:从 5% → 20% → 50% → 100%
- 监控指标:错误率、延迟、业务指标
- 快速回滚:发现问题立即停止
- Feature Flags:简化版的灰度,用开关控制
灰度发布是降低上线风险的重要手段,特别是对 AI 项目来说,模型切换和功能变更需要谨慎验证。