功能开关 Feature Flag 实践:让上线和发布解耦
很多团队把“代码上线”和“功能发布”当成一件事。
这会带来一个问题:
1 | 只要代码上线,功能就立刻暴露给所有用户。 |
如果功能有 bug,只能回滚整次发布。
如果只想给部分用户试用,也很难控制。
功能开关的价值,就是把这两件事拆开:
1 | 代码可以先上线 |
这对真实项目很有用。
一、什么是功能开关
功能开关就是一个判断:
1 | 这个功能现在要不要启用? |
代码里可能长这样:
1 | if (featureFlags.newCheckout) { |
或者后端:
1 | if flags.Enabled(ctx, "new-pricing") { |
这看起来很简单。
但如果设计得好,它能解决很多发布问题。
二、功能开关解决什么问题
常见用途:
- 新功能灰度发布
- 临时关闭故障功能
- A/B 测试
- 内部用户先体验
- 按租户开放功能
- 按地区开放功能
- 大重构期间新旧逻辑并存
比如你重写了支付页。
没有功能开关时:
1 | 上线 = 所有人使用新版支付页 |
有功能开关时:
1 | 先上线代码 |
风险会小很多。
三、最简单的开关可以从配置开始
小项目不需要一上来就接复杂平台。
可以先从配置文件或环境变量开始:
1 | FEATURE_NEW_CHECKOUT=false |
代码读取:
1 | const newCheckoutEnabled = process.env.FEATURE_NEW_CHECKOUT === "true"; |
这种方式适合:
- 本地开发开关
- 环境级别开关
- 不需要动态调整的功能
缺点也明显:
- 修改需要重启或重新部署
- 不能按用户灰度
- 没有管理界面
- 没有变更记录
所以它适合起步,不适合复杂灰度。
四、动态开关要有规则
稍微复杂一点的功能开关,需要支持规则。
比如:
1 | 内部用户打开 |
可以设计一个规则结构:
1 | { |
判断时传入上下文:
1 | { |
功能开关不是一个全局布尔值。
真正有价值的是:
1 | 根据用户、租户、环境和比例做判断。 |
五、百分比灰度要稳定
百分比灰度不要每次随机。
错误示例:
1 | return Math.random() < 0.1; |
这样用户刷新一次,可能进新版。
再刷新一次,又回旧版。
体验会很混乱。
更好的方式是用稳定 hash:
1 | hash(user_id + feature_key) % 100 < percentage |
这样同一个用户对同一个功能的结果稳定。
当比例从 10% 调到 20% 时,只会新增一部分用户,不会每次乱跳。
这点很重要。
灰度发布不是抽奖。
六、开关判断要放在边界处
不要让功能开关散落到业务深处。
比如:
1 | controller 判断一次 |
这样很容易变成混乱的分支地狱。
更好的做法是在边界处决定走哪条路径:
1 | if (flags.enabled("new-checkout", user)) { |
也就是说:
1 | 入口处选择新旧逻辑 |
这会让后续清理开关容易很多。
七、每个开关都要有负责人和过期时间
功能开关最大的问题是:
1 | 开了以后没人删。 |
几年后代码里全是:
1 | if old_flag |
没人敢动。
所以创建开关时,最好记录:
- 开关名称
- 负责人
- 创建时间
- 预期删除时间
- 类型
- 默认值
- 影响范围
例如:
1 | new-checkout |
功能开关不是永久配置。
它应该有生命周期。
八、区分几类开关
不要把所有开关都当成一种东西。
常见类型:
1 | release flag:发布开关 |
发布开关用于灰度新功能,稳定后应该删除。
实验开关用于 A/B 测试,实验结束后应该固化结果。
运维开关用于临时降级,比如关闭推荐系统。
权限开关用于不同用户或套餐能力,生命周期可能很长。
不同开关的管理方式不一样。
不要把临时发布开关写成永久权限逻辑。
九、开关也要可观察
如果一个功能通过开关灰度,你至少要知道:
- 当前是否开启
- 对哪些用户开启
- 命中了多少请求
- 新旧路径错误率差异
- 新旧路径耗时差异
- 谁在什么时候改了规则
否则灰度就只是心理安慰。
日志里可以加:
1 | feature.new_checkout=true |
或者在指标里按开关维度区分。
这样出问题时,才能快速判断是不是新功能导致的。
十、前端开关不要泄露敏感能力
前端功能开关只能控制展示。
不能控制权限。
比如按钮隐藏:
1 | 用户看不到删除按钮 |
这不等于用户不能调用删除接口。
真正的权限必须在后端验证。
前端开关适合:
- 显示新版页面
- 切换 UI
- 控制实验体验
后端仍然要做:
- 权限判断
- 套餐判断
- 数据范围判断
不要把前端 feature flag 当成安全边界。
十一、我的建议
功能开关可以从简单做起:
1 | 第一阶段:环境变量开关 |
不要为了一个小项目一上来搭复杂平台。
但也不要把所有功能都和部署绑死。
真正有价值的功能开关,应该做到:
- 代码上线和功能发布解耦
- 支持小范围灰度
- 出问题能快速关闭
- 有负责人和过期时间
- 能观察新旧逻辑差异
- 稳定后及时清理
功能开关不是让代码里多几个 if。
它是让发布变得更可控。