系统设计面试:如何设计一致且可回滚的 Feature Flag 评估平台?
题干与适用场景
平台要为多租户服务提供布尔、字符串和结构化 Feature Flag,规则可能依赖租户、用户、地区和应用版本。配置发布后,有的实例立即生效,有的实例延迟数分钟;同时不能让敏感上下文进入日志。请设计控制面、数据面、评估上下文、缓存、发布、故障和审计闭环。
面试官考察点
- 能否区分控制面发布与数据面本地评估,并定义版本和一致性目标。
- 是否正确处理 evaluation context 的合并、覆盖、传播和隐私。
- 能否设计缓存失效、离线快照、默认值和回滚,而不是依赖实时 RPC。
- 是否把曝光、错误、变更和规则命中做成可审计且不泄露个人信息的遥测。
回答前需要澄清的问题
- Flag 评估必须强一致,还是允许短暂旧版本?最大陈旧时间是多少?
- 规则按租户、用户、设备、地区和版本的优先级如何定义?
- 服务在控制面不可用时必须继续运行多久?默认值由谁批准?
- 评估上下文含哪些个人信息,哪些字段可以进入曝光日志?
- 需要跨语言 SDK、本地离线评估,还是统一远程评估服务?
30 秒回答框架
我会把控制面负责规则校验、版本化发布、审批和回滚,数据面 SDK 使用带版本的本地快照评估。上下文按全局、请求和调用级合并,并明确覆盖规则;只传最小必要字段。实例通过流或轮询收到版本更新,缓存带 TTL 和版本号;控制面故障时继续使用最后可信快照并暴露陈旧指标。每次评估记录 flag、版本、结果和匿名关联键,禁止记录原始个人属性。
分步骤深入解答
第一步:定义对象和发布状态机
Flag 包含类型、默认值、规则、变体、环境、版本和生效时间。发布流程经过草稿、校验、审批、灰度、完成或回滚,每次变更生成不可变版本。规则编译失败、类型不匹配或默认值缺失时阻止发布,不把运行时错误推给所有服务。
第二步:设计评估上下文
把应用、主机、区域、租户和用户属性作为结构化 context。全局、事务和调用级上下文按规范合并,重复键采用明确的覆盖顺序;SDK 不应隐式读取任意线程变量而让结果不可解释。敏感字段在进入 SDK、传输和日志前做白名单和脱敏。
第三步:选择本地或远程评估
低延迟和控制面短暂故障要求本地评估,因此下发规则快照和版本。远程评估适合规则频繁变更或逻辑集中,但每次请求增加网络和可用性依赖。可以让 SDK 本地评估,复杂规则由 provider 提供,接口保持类型化并返回原因、版本和元数据。
第四步:建立缓存与一致性目标
快照缓存以环境、flag 集合和版本为键,带校验和与过期时间。更新通知丢失时由轮询补偿,实例启动先加载最后可信快照,再异步追赶新版本。定义“版本不回退”“最大陈旧秒数”和“回滚传播时间”,并把它们作为 SLO。
第五步:处理故障与安全默认值
评估异常时返回类型匹配的默认值或上一个可信值,并标记 reason、error code 和 source。关键支付、权限或数据删除开关不应静默使用危险默认值,可选择阻断请求或人工批准的 fail-closed 策略。SDK 需要防止未知 flag、类型转换和规则执行超时扩散。
第六步:设计审计和曝光遥测
控制面记录谁在何时发布、审批、灰度和回滚。数据面记录 flag key、版本、结果、规则分支、SDK 版本和匿名 subject hash;不写入原始邮箱、IP 或完整 context。采样、保留和访问权限按租户隔离,结果可与指标关联以识别灰度影响。
第七步:验证回滚与迁移
用固定 context 回放规则版本,验证不同语言 SDK 的结果一致。演练通知丢失、缓存损坏、控制面不可用、时钟偏差、部分实例回滚和 provider 升级。回滚必须生成新版本而不是改写旧版本,完成后比较实例版本分布和业务指标。
高质量示范回答
控制面负责类型校验、规则编译、审批、版本和灰度;数据面 SDK 使用带校验和的本地快照评估,以满足低延迟和离线运行。评估 context 按全局、事务、调用级合并,重复键覆盖顺序固定,敏感字段白名单化。实例通过通知加轮询获取版本,缓存定义最大陈旧时间和不回退约束。异常返回类型匹配的默认或上个可信值,并区分关键开关的 fail-closed。审计保留发布和回滚链路,曝光日志只含版本、结果和匿名关联键,回滚用新版本并通过多 SDK 回放与故障演练验证。
常见错误
- 每次评估都同步调用控制面,令业务延迟和可用性依赖配置服务。
- 只缓存值不缓存版本,无法判断实例是否回退或陈旧。
- 让上下文合并顺序依赖语言 SDK 的隐式行为,导致跨服务结果不同。
- 在日志中记录完整用户属性或邮箱,造成隐私泄露。
- 回滚直接覆盖旧配置,失去审计、重放和部分实例恢复能力。
追问及应对
追问一:配置服务挂了还能评估吗?
使用最后可信快照继续评估,并报告版本、陈旧时间和 source;超过最大陈旧阈值时按 flag 风险选择默认、阻断或人工处理,而不是无限期静默运行。
追问二:如何保证多语言 SDK 一致?
定义规范化规则、类型、context 合并和错误原因,提供跨语言固定输入/输出向量与版本化测试。复杂规则由 provider 统一执行,SDK 只负责协议和生命周期。
追问三:灰度比例如何稳定?
用稳定的匿名 subject key 和明确哈希算法分桶,规则版本固定后同一 subject 在不同实例得到同一变体。变更算法或盐值必须生成新版本并说明迁移影响。
追问四:为什么需要 hooks?
Hook 可在评估前补充上下文、评估后校验值或发送遥测,但必须有明确顺序、超时和错误策略。它不能偷偷改变 flag 类型或绕过审计。
追问五:如何安全删除一个 flag?
先查代码引用、评估流量和默认分支,发布一个固定值版本,观察一段时间后再删除规则和 SDK 元数据。保留历史版本和迁移记录,避免旧实例请求未知 flag 时出现类型错误。