灰度切流策略框架设计
一套基于策略模式 配置驱动的通用灰度发布框架支持白名单、黑名单、百分比、组合策略等多种切流模式配置热更新零代码侵入。1. 背景与问题在大型业务系统中新功能上线通常不能一蹴而就地全量放开而是需要经历内部验证 → 小范围灰度 → 逐步放量 → 全量切流的渐进式发布过程。传统的 if-else 硬编码方式存在以下问题问题描述代码侵入强每个灰度场景都需要写一套判断逻辑散落在各业务代码中调整需发版修改白名单或百分比需要重新发布应用缺乏一致性不同场景的灰度实现方式五花八门难以统一管控无法一键熔断发现问题时无法快速关闭灰度开关难以组合无法灵活组合多种灰度策略如白名单 百分比本框架的核心目标一行代码接入、配置动态生效、策略可组合、一键可熔断。2. 整体架构┌─────────────────────────────────────────────────────────────┐ │ 业务调用方 │ │ grayStrategyService.isPassGrayStrategy( │ │ YOUR_BIZ_CODE, targetId, params) │ └────────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ GrayStrategyServiceImpl │ │ │ │ ① 根据 bizCode 查询远程配置中心获取 GrayStrategyConfig │ │ ② 检查 switchOn 总开关 │ │ ③ 反序列化 modeList逐个执行策略 Handler │ │ ④ all pass → true ; any fail → false │ │ ⑤ try-finally 打印决策 digest 日志 │ └────────────┬────────────┬────────────┬──────────────────────┘ │ │ │ ┌────────▼──┐ ┌──────▼───┐ ┌────▼──────────┐ │ Handler A │ │Handler B │ │ Handler C │ ... │ (白名单) │ │(百分比) │ │(黑名单等) │ └───────────┘ └──────────┘ └───────────────┘核心决策流程业务方传入bizCode灰度业务码、targetId通常是商户 ID、params维度参数根据bizCode从配置中心加载GrayStrategyConfig如果配置不存在或switchOnfalse直接返回默认结果遍历modeList中的策略模式逐个执行对应的Handler任意一个 Handler 不通过整体不通过AND 语义全部通过返回true3. 核心组件详解3.1 配置模型 —— GrayStrategyConfig每一条灰度配置包含 5 个字段字段类型说明示例bizCodeString灰度业务标识定位到具体业务场景SUPPLY_B2B_MEMBER_WITH_REGISTER_FROMswitchOnString总开关控制该场景灰度是否启用true/falsemodeListStringJSON 数组策略模式列表支持多策略组合[WHITE_LIST,UID_PERCENTAGE]ruleMapStringJSON 对象白名单/黑名单的维度规则{userId:[uid1,uid2],instCode:[Z05]}cutPercentageString百分比切流比例0-10010表示 10%关键设计 —— modeList 是数组同一个业务场景可以叠加多种策略模式。比如同时配置[WHITE_LIST,UID_PERCENTAGE]表示先过白名单、再过百分比全部满足才算通过。配置存储在远程配置中心如 ComPara修改后即时生效无需发版。3.2 策略模式枚举 —— GrayStrategyModeEnum将每种灰度策略封装为枚举值 Handler的形式┌─────────────────────────────┬───────────────────────────────────────────────────┐ │ 枚举值 │ 描述 │ ├─────────────────────────────┼───────────────────────────────────────────────────┤ │ ALL_PASS │ 全量放行模式 │ │ WHITE_LIST │ 白名单模式所有维度必须同时命中AND 语义 │ │ BLACK_LIST │ 黑名单模式任意维度命中即拦截OR 语义 │ │ UID_PERCENTAGE │ UID 分表位百分比切流确定性同一用户结果恒定 │ │ REFERENCE_UID_PERCENTAGE │ 引用 UID 百分比切流hashCode 分桶非标准 UID 用 │ │ WHITE_LIST_ANY_MATCH_ENOUGH │ 宽松白名单任意一个维度命中即通过OR 语义 │ │ WHITE_LIST_WITH_PERCENTAGE │ 白名单百分比核心维度直通非核心维度再过百分比 │ └─────────────────────────────┴───────────────────────────────────────────────────┘设计亮点 —— 枚举即路由表枚举的构造函数第三个参数直接持有对应的 Handler 实例WHITE_LIST(WHITE_LIST,白名单模式,newWhiteListStrategyHandler()),调用时通过GrayStrategyModeEnum.getByCode(mode).getHandler()即可获取处理器省去了工厂类或 Map 映射。3.3 策略处理器接口 —— GrayStrategyHandlerpublicinterfaceGrayStrategyHandler{intPERCENTAGE_00;intPERCENTAGE_100100;/** * param config 灰度策略配置含 ruleMap、cutPercentage 等 * param targetId 切流目标 ID通常是商户 userId * param param 运行时维度参数如 userId、instCode、label 等 * return true: 通过灰度决策 | false: 不通过 */booleanhandle(GrayStrategyConfigconfig,StringtargetId,MapString,Stringparam);}三个参数的职责明确config该场景的完整灰度配置Handler 按需读取 ruleMap、cutPercentage 等字段targetId切流目标通常传商户 userIdparam运行时维度参数与 ruleMap 中的 key 对应用于匹配3.4 调用方接口 —— GrayStrategyService提供两个重载方法和一系列参数构建工具// 核心方法defaultResult 默认 falsebooleanisPassGrayStrategy(StringbizCode,StringtargetId,MapString,Stringparams);// 支持自定义默认结果的方法booleanisPassGrayStrategy(StringbizCode,StringtargetId,MapString,Stringparams,booleandefaultResult);// 参数构建工具方法staticstaticMapString,StringbuildUidParamMap(StringuserId);staticMapString,StringbuildLabelParamMap(Stringlabel);staticMapString,StringbuildBizSceneParamMap(StringbizScene);staticMapString,StringbuildInstCodeParamMap(StringinstCode);staticMapString,StringbuildParamMap(Stringkey,Stringvalue);defaultResult** 参数的含义** 当配置不存在即配置中心没有该 bizCode 的记录时返回什么默认值。一般传false保守走老流程也可以按业务需要传true。4. 七种策略模式详解4.1 ALL_PASS —— 全量放行语义无条件放行所有请求都通过。适用场景灰度验证已结束需要全量上线时将 modeList 改为[ALL_PASS]即可。实现直接返回true。4.2 WHITE_LIST —— 严格白名单AND 语义语义ruleMap 中所有维度都必须命中白名单才算通过。决策流程传入参数: {userId: A, instCode: Z05} 配置: ruleMap {userId: [A,B], instCode: [Z05]} 遍历 ruleMap: ① userId 维度: param[userId]A 在白名单 [A,B] 中 → 通过 ② instCode 维度: param[instCode]Z05 在白名单 [Z05] 中 → 通过 所有维度通过 → 返回 true传入参数: {userId: A, instCode: Z99} 配置: ruleMap {userId: [A,B], instCode: [Z05]} 遍历 ruleMap: ① userId 维度: 通过 ② instCode 维度: param[instCode]Z99 不在白名单 [Z05] 中 → 不通过 存在维度不通过 → 返回 false注意如果传入参数中缺少某个维度的值直接返回false。白名单是证明你行的逻辑缺少证据即为不通过。适用场景精确控制特定商户需要同时满足多个维度条件。4.3 BLACK_LIST —— 黑名单OR 语义语义ruleMap 中任意一个维度命中黑名单即被拦截。决策流程传入参数: {userId: A, instCode: Z05} 配置: ruleMap {userId: [C,D], instCode: [Z99]} 遍历 ruleMap: ① userId 维度: param[userId]A 不在黑名单 [C,D] 中 → 不拦截 ② instCode 维度: param[instCode]Z05 不在黑名单 [Z99] 中 → 不拦截 全部未命中 → 返回 true放行关键设计差异如果传入参数中缺少某个维度的值黑名单模式选择continue跳过不拦截而非返回false。这与白名单相反黑名单是证明你不行缺少证据就不拦截。适用场景排除特定商户如已知有问题的商户 ID 需要暂时屏蔽新流程。4.4 UID_PERCENTAGE —— UID 分表位百分比切流语义根据 userId 的分表位0-99判断是否命中灰度比例。决策流程1. 从配置读取 cutPercentage如 10 2. 通过 DBPrimaryKeyUtil.getUserPartitionByUserId(userId) 获取分表位 3. 判断: userPartition cutPercentage → 通过设计亮点 —— 确定性使用分表位而不是Random.nextInt()保证了同一用户多次请求的结果恒定。这是灰度切流的关键要求——用户不能一会儿走新流程一会儿走老流程。分表位算法内部还兼容了压测 UID倒数第二位可能是字母 A-J会做字母→数字的替换处理。适用场景按比例灰度放量如先放量 5%观察后逐步提升到 10%、50%、100%。4.5 REFERENCE_UID_PERCENTAGE —— 引用 UID 百分比切流语义与 UID_PERCENTAGE 类似但分桶算法不同。关键区别模式分桶算法适用场景UID_PERCENTAGE分表位DB 内部 ID 格式标准 ipay 内部 UIDREFERENCE_UID_PERCENTAGEhashCode() % 100非 ipay 格式的引用 ID如平台商的 seller ID设计亮点 —— 模板方法模式继承PercentageStrategyHandler只覆写getUserPartitionByUserId方法publicclassReferenceUidPercentageStrategyHandlerextendsPercentageStrategyHandler{OverrideprotectedintgetUserPartitionByUserId(StringreferenceUserId){returnMath.abs(referenceUserId.hashCode()%100);}}父类定义了百分比判定的骨架流程子类只需替换分桶算法。4.6 WHITE_LIST_ANY_MATCH_ENOUGH —— 宽松白名单OR 语义语义ruleMap 中任意一个维度命中白名单即算通过。决策流程传入参数: {userId: A, instCode: Z99} 配置: ruleMap {userId: [A,B], instCode: [Z05]} 遍历 ruleMap: ① userId 维度: param[userId]A 在白名单 [A,B] 中 → 命中 任意一条命中 → 返回 true无需继续检查 instCode额外能力 —— 通配符*如果某个维度的白名单列表包含*则该维度无条件通过配置: ruleMap {instCode: [*]} 传入参数: {instCode: 任意值} → 命中通配符直接通过与严格白名单的对比对比维度WHITE_LIST严格WHITE_LIST_ANY_MATCH_ENOUGH宽松语义所有维度都要命中任意维度命中即可逻辑ANDOR缺少维度参数返回 false不影响其他维度命中即可通配符支持无支持*钩子方法设计hintPercentageAfterMatch()是一个钩子当前实现直接返回true但为子类留了扩展点。4.7 WHITE_LIST_WITH_PERCENTAGE —— 白名单 百分比语义先做白名单匹配OR 语义命中后根据维度类型决定是否再做百分比淘汰。核心逻辑覆写钩子方法白名单命中后: ├── 命中的维度是核心维度 (userId / maInnerId) │ → 直接放行不走百分比 │ └── 命中的维度是非核心维度 (instCode / label 等) → 还需要过百分比随机淘汰 → RandomUtils.nextInt(0, 101) cutPercentage → 通过设计智慧 —— 维度优先级区分核心维度userId、maInnerId白名单精确到具体商户确定性高不需要百分比。配置了某个 userId 就一定放行该用户。非核心维度instCode、label 等白名单覆盖范围大如某个 instCode 下可能有大量商户需要百分比来渐进放量避免一次性放开太多。百分比注意点这里的百分比用的是RandomUtils.nextInt()随机与UID_PERCENTAGE的分表位确定性不同。这是有意为之——白名单阶段已经做了维度筛选百分比阶段是对非核心维度的补充过滤随机性可以接受。5. 策略组合机制modeList支持配置多个策略模式实现灵活组合5.1 多策略 AND 语义配置示例modeList: [WHITE_LIST,UID_PERCENTAGE]决策过程先过白名单 → 再过百分比 → 全部通过才算通过请求进入 │ ▼ WHITE_LIST Handler ├── 不通过 → 返回 false └── 通过 ↓ ▼ UID_PERCENTAGE Handler ├── 不通过 → 返回 false └── 通过 → 返回 true5.2 单策略场景配置示例modeList: [WHITE_LIST_ANY_MATCH_ENOUGH]只走一种策略模式通过即通过。5.3 全量切流配置示例modeList: [ALL_PASS]等价于关闭灰度所有请求都走新流程。也可以直接将switchOn设为true配合ALL_PASS。6. 核心决策引擎流程privatebooleandoGrayDecide(StringbizCode,StringuserId,MapString,Stringparams,booleandefaultResult){// 1. bizCode 为空 → 返回 defaultResultif(Objects.isNull(bizCode)){returndefaultResult;}// 2. 从配置中心查询 GrayStrategyConfigGrayStrategyConfigstrategyConfigqueryGrayStrategyConfig(bizCode);// 3. 配置不存在 → 返回 defaultResultif(Objects.isNull(strategyConfig)){returndefaultResult;}// 4. 总开关关闭 → 返回 false一键熔断if(!Boolean.parseBoolean(strategyConfig.getSwitchOn())){returnfalse;}// 5. 解析 modeListListStringgrayModesJSONObject.parseObject(strategyConfig.getModeList(),newTypeReferenceListString(){});if(CollectionUtils.isEmpty(grayModes)){returnfalse;}// 6. 遍历执行每个策略 HandlerAND 语义for(StringgrayMode:grayModes){GrayStrategyModeEnummodeEnumGrayStrategyModeEnum.getByCode(grayMode);if(Objects.isNull(modeEnum)){returnfalse;// 不支持的模式 → 不通过}if(!modeEnum.getHandler().handle(strategyConfig,userId,params)){returnfalse;// 任一策略不通过 → 整体不通过}}returntrue;// 所有策略都通过}关键设计决策总结步骤条件结果设计意图bizCode 为空无法决策返回defaultResult兜底安全由调用方决定配置不存在未配置灰度规则返回defaultResult未配置 ! 拦截尊重业务方的默认倾向switchOnfalse主动关闭返回false一键熔断紧急情况下快速回滚modeList 为空无策略返回false有配置但无策略保守处理不支持的模式配置错误返回false防止配置错误导致误放行Handler 不通过策略拦截返回falseAND 语义任意失败即终止7. 配置示例示例 1简单白名单仅允许指定用户{grayBizCode:MY_FEATURE_SWITCH,switchOn:true,modeList:[\WHITE_LIST\],ruleMap:{\userId\:[\2088123456\,\2088789012\]},cutPercentage:0}调用方式grayStrategyService.isPassGrayStrategy(MY_FEATURE_SWITCH,userId,GrayStrategyService.buildUidParamMap(userId));示例 2白名单 百分比组合白名单用户直通非白名单用户按 instCode 10% 放量{grayBizCode:MY_FEATURE_SWITCH,switchOn:true,modeList:[\WHITE_LIST_WITH_PERCENTAGE\],ruleMap:{\userId\:[\2088123456\],\instCode\:[\Z05\]},cutPercentage:10}调用方式MapString,StringparamsnewHashMap();params.put(userId,userId);params.put(instCode,instCode);grayStrategyService.isPassGrayStrategy(MY_FEATURE_SWITCH,userId,params);userId 命中白名单 → 核心维度直通instCode 命中白名单但 userId 未命中 → 非核心维度10% 随机放量都未命中 → 不通过示例 3全量切流{grayBizCode:MY_FEATURE_SWITCH,switchOn:true,modeList:[\ALL_PASS\],ruleMap:{},cutPercentage:0}示例 4黑名单排除排除特定商户{grayBizCode:MY_FEATURE_SWITCH,switchOn:true,modeList:[\BLACK_LIST\],ruleMap:{\userId\:[\2088problem1\,\2088problem2\]},cutPercentage:0}示例 5多策略组合先过白名单再过百分比{grayBizCode:MY_FEATURE_SWITCH,switchOn:true,modeList:[\WHITE_LIST\,\UID_PERCENTAGE\],ruleMap:{\instCode\:[\Z05\]},cutPercentage:30}instCode 必须是 Z05白名单 AND 语义且分表位 30百分比切流两者都满足才通过8. 灰度生命周期管理一个完整的灰度切流应经历以下阶段阶段 1: 内部验证 配置: modeList[WHITE_LIST], ruleMap{userId: [内部测试UID]} 目标: 仅对内部用户开放 ↓ 验证通过 阶段 2: 小范围灰度 配置: modeList[WHITE_LIST_ANY_MATCH_ENOUGH], ruleMap{instCode: [Z05]} 目标: 指定 instCode 下的商户开放 ↓ 观察稳定 阶段 3: 渐进放量 配置: modeList[WHITE_LIST_WITH_PERCENTAGE], ruleMap{instCode: [Z05]}, cutPercentage10 目标: 非核心维度命中后 10% 放量 ↓ 逐步提升 cutPercentage: 10 → 30 → 50 → 80 阶段 4: 全量切流 配置: modeList[ALL_PASS] 目标: 全量开放 ↓ 运行稳定 阶段 5: 下线灰度代码 目标: 移除灰度判断逻辑删除 ComPar配置清理 GrayBizCodeEnum重要提醒阶段 5 常常被忽略。灰度配置不是永久的切流完成后要及时下线灰度代码和配置否则代码中会残留大量无用的灰度判断逻辑增加维护成本。9. 设计模式总结设计模式应用位置效果策略模式GrayStrategyHandler 7 种实现不同灰度判定算法可独立变化互不影响模板方法模式PercentageStrategyHandler→ReferenceUidPercentageStrategyHandler复用百分比切流骨架只替换分桶算法模板方法模式AnyMatchEnoughWhiteListStrategyHandler→AnyMatchEnoughWhiteListAndPercentageStrategyHandler复用白名单匹配逻辑只扩展命中后的百分比判断枚举即路由表GrayStrategyModeEnum持有 Handler 实例省去工厂类枚举值 策略码 处理器配置驱动配置中心存储 GrayStrategyConfig策略规则热更新无需发版组合模式modeList 数组多个策略 AND 组合实现精细化切流控制10. 接入指南10.1 新增灰度场景Step 1在GrayBizCodeEnum中添加业务码枚举可选建议添加以获得编译期检查publicenumGrayBizCodeEnum{// .../** 你的业务场景描述 */YOUR_BIZ_SCENE_CODE,// ...}Step 2在配置中心ComPara添加iexpmprodGrayStrategyConfig配置项grayBizCode填写你的业务码。Step 3在业务代码中调用SofaReferenceprivateGrayStrategyServicegrayStrategyService;publicvoidyourBusinessMethod(){StringuserIdgetCurrentUserId();booleanisNewFlowgrayStrategyService.isPassGrayStrategy(GrayBizCodeEnum.YOUR_BIZ_SCENE_CODE.name(),userId,GrayStrategyService.buildUidParamMap(userId),false// 配置不存在时默认走老流程);if(isNewFlow){// 新流程}else{// 老流程}}10.2 新增灰度模式如果现有 7 种模式不满足需求扩展步骤如下Step 1实现GrayStrategyHandler接口publicclassYourCustomHandlerimplementsGrayStrategyHandler{Overridepublicbooleanhandle(GrayStrategyConfigconfig,StringtargetId,MapString,Stringparam){// 你的灰度判定逻辑returntrue;// or false}}Step 2在GrayStrategyModeEnum中添加枚举值YOUR_MODE(YOUR_MODE,你的模式描述,newYourCustomHandler()),Step 3在配置中心的 modeList 中使用YOUR_MODE即可。10.3 参数构建速查场景构建方法按 userId 灰度GrayStrategyService.buildUidParamMap(userId)按 instCode 灰度GrayStrategyService.buildInstCodeParamMap(instCode)按 label 灰度GrayStrategyService.buildLabelParamMap(label)按 bizScene 灰度GrayStrategyService.buildBizSceneParamMap(bizScene)按 loginId 灰度GrayStrategyService.buildLoginIdParamMap(loginId)按 salePlanCode 灰度GrayStrategyService.buildSalePlanCodeParamMap(salePlanCode)自定义维度GrayStrategyService.buildParamMap(yourKey, yourValue)多维度组合自行构建 Map 并 put 多个 key-value11. 最佳实践11.1 命名规范灰度业务码应清晰表达场景语义推荐格式{业务域}_{功能描述}_{灰度目的}✅ SUPPLY_B2B_MEMBER_WITH_REGISTER_FROM (绑定会员指定 registerFrom) ✅ PORTAL_ONBOARD_STRUCTURE_CHANGE_ISNTCODE (门户入驻解耦-主体维度) ✅ MERCHANT_TRANSACTION_RISK_LIMIT (商户交易限制) ✅ ONBOARD_HK_Z05_SWITCH (香港商户切流到 Z05)11.2 defaultResult 的选择保守策略推荐传false配置不存在时走老流程。适用于新功能上线初期。激进策略传true配置不存在时走新流程。适用于老流程即将下线的场景。11.3 日志可观测每次灰度决策都有 digest 日志输出[GrayStrategyService-digest]bizCodeYOUR_BIZ_CODE,targetId2088xxx,params{userId2088xxx},isPassGrayStrategytrue.利用这个日志可以进行线上灰度效果统计和问题排查。11.4 切流节奏建议1% → 观察 2-3 天 → 5% → 观察 2-3 天 → 10% → 30% → 50% → 80% → 100% → ALL_PASS每次调整百分比只需修改配置中心的cutPercentage值即时生效。12. 类图总览GrayStrategyService (接口) │ └── GrayStrategyServiceImpl (决策引擎) │ ├── GrayStrategyConfig (配置模型) │ bizCode / switchOn / modeList / ruleMap / cutPercentage │ ├── GrayStrategyModeEnum (策略路由) │ ALL_PASS ────→ DefaultStrategyHandler │ WHITE_LIST ──→ WhiteListStrategyHandler │ BLACK_LIST ──→ BlackListStrategyHandler │ UID_PERCENTAGE → PercentageStrategyHandler │ └── ReferenceUidPercentageStrategyHandler │ WHITE_LIST_ANY_MATCH_ENOUGH → AnyMatchEnoughWhiteListStrategyHandler │ └── AnyMatchEnoughWhiteListAndPercentageStrategyHandler │ WHITE_LIST_WITH_PERCENTAGE ──→ (同上) │ └── GrayBizCodeEnum (业务码) FIX_ALI_TRIP_SETTLE_CHANNEL SUPPLY_B2B_MEMBER_WITH_REGISTER_FROM PORTAL_ONBOARD_STRUCTURE_CHANGE_ISNTCODE ... (40 业务码)