简介:《数据中台-数据决策平台》演示文稿从业务和技术两个角度系统梳理了在线决策平台的整体设计,适用于金融风控、大数据产品及方案架构师快速掌握数据采集、指标加工、规则引擎、流程调度等核心模块。内容涵盖系统定位、技术架构、功能模块、指标体系、规则引擎配置及历年建设演进;规则引擎部分重点解析规则包、操作规则、决策表、决策流如何串联成完整决策单元,指标部分说明行内数仓/ECIF与行外征信、公积金、社保等多源数据的整合方式。同时展示2020年消费金融、小微易贷等场景的业务成效,以及从SMG3到IBM ODM各版本的建设历程,可作项目汇报、内部培训或方案设计的参考底稿。资源为 1 个 PPTX 演示文稿,压缩包约 1015KB,以架构图、流程图和功能说明为主,便于直接翻阅学习。已有 181 人学习浏览,适合需要建立数据决策平台全局认知的读者参考。
1. 数据中台-数据决策平台:先搞清它到底是“数据平台”还是“决策系统”
信贷审批场景里最能看出这套东西的定位:业务系统发起授信申请,调度平台在几秒内把数仓的历史指标、征信社保等外部数据全部捞齐,加工成模型要的变量,再交给规则引擎跑出决策结果,最后把指标和决策结果异步推给 Kafka 落地。整个过程走完,业务端拿到的是一个可直接展示的决策结论,而不是一堆报表和图表。这说明它本质上是“规则引擎 + 指标加工 + 流程调度”三件套的组合体,数据中台只是它的底座,数据决策才是核心价值。适合正在搭风控决策平台、想做规则可视化配置、又不想从零写引擎的团队参考。
2. 链路与采集:从 Kafka 异步接入到流立方清洗,先把口径对齐
2.1 一条授信请求在平台里到底怎么流转
我们拆开原文里的主链路,把每一步的职责落到具体组件上。业务系统发起授信申请后,调度平台先通知数据采集清洗引擎开始干活;采集引擎兵分两路,一路通过统一数据接入平台拿外部数据(征信、社保、公积金、地税),一路从数仓和指标库拉历史指标;数据汇齐后推给计算引擎做运算,再拿回中间变量做清洗加工;最后调度平台带着加工好的指标数据去找规则引擎,规则引擎出决策结果,调度平台把结果回给业务系统,同时把指标和决策结果推到 Kafka 队列,由持久化引擎异步落地。
这段链路里最容易被忽视的角色是 Kafka。它承担的不是实时计算,而是异步解耦——决策结果不直接写库,而是进队列,由持久化引擎慢慢消费。这样设计的好处是:决策主链路不用等数据库写入,业务系统的响应时间不会被持久化拖累。常见做法是给不同业务场景建不同的 topic,比如授信决策结果一个 topic、贷中预警一个 topic,消费者按业务线分开部署,避免某个场景消费慢了影响其他场景。
Kafka 消费端如果要控制背压,代码层面一般这么处理:
# 持久化引擎消费端配置示例(伪代码) consumer_config = { "bootstrap.servers": "node1:9092,node2:9092,node3:9092", "group.id": "decision-persist-group", "enable.auto.commit": False, # 手动提交,防止批量入库失败丢数据 "max.poll.records": 500, # 单次拉取上限,避免内存撑爆 "max.poll.interval.ms": 300000 # 单次处理超时上限,超时会触发rebalance } def consume_loop(): while True: records = consumer.poll(timeout=1.0) batch = [r.value() for r in records] persist_batch_to_db(batch) # 批量写入,失败则重试 consumer.commit() # 写入成功后再提交offset这段配置里两个参数很关键:enable.auto.commit必须关掉,否则持久化引擎宕机时会丢数据;max.poll.records要结合单条数据大小和入库吞吐来调,500 只是一个起点,如果单条决策结果有几十个指标字段,建议降到 200 左右。这样做的目的,是保证决策结果从 Kafka 到数据库的链路具备“至少一次”的投递语义,配合目的地表的幂等键(比如用申请单号 + 决策版本号做唯一索引),就能做到不重不漏。
2.2 批处理与实时指标:数仓 + 流式引擎的分工
指标数据有两种加工路径,原文说得明确:数仓每日做批处理加工,流式计算引擎(流立方)做实时指标加工。批处理路径覆盖的是历史类指标,比如客户近 12 个月的授信额度使用率、企业贷核心数仓的存量余额这些;实时路径覆盖的是本次请求会话内产生的指标,典型如“客户最近申请的设备信息”“最近 N 小时申请次数”。
这两类指标在采集清洗引擎里汇合后,统一推给计算引擎做运算。这里有个容易踩的坑:同一个指标如果批处理和实时两条链路都能产出,必须明确优先取哪条。常见做法是设一个指标来源优先级配置表,比如“近 1 小时申请次数”只认流立方,“近 12 个月月均余额”只认数仓,禁止同一个指标双写。因为一旦两边数值对不上,规则引擎拿到哪个值都会引发线上决策不一致,这个后面避坑章节细说。
清洗加工环节常见做法是:把原始数据先落成标准化字段,再做缺失值填充和异常值剔除。比如外部数据源返回的婚姻状态字段,不同数据源给的值域不一样(有的是“已婚/未婚”,有的是“M/S”),在指标层就要先做字典映射,统一编码后再进入特征计算。这块的配置一般会放在采集任务里:
{ "source": "GJJ_HANGZHOU", "fields": [ {"raw": "gjj_balance", "target": "acc_balance", "type": "decimal", "missing": -1}, {"raw": "gjj_pay_status", "target": "pay_status", "type": "string", "mapping": {"N": "0", "Y": "1", "": "-1"}} ], "calc_rules": { "acc_balance_monthly_avg": "AVG(acc_balance) OVER (PARTITION BY cust_id ORDER BY month DESC ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)" } }字段映射的核心逻辑是"missing": -1和"": "-1"这两行——外部数据源经常返回空值,如果不统一转成 -1,后面特征工程里任何数值运算都会碰到 NaN 或者空字符串,规则引擎执行时容易报类型错误。这个配置在采集清洗引擎启动时加载,新数据源接入只需要改配置,不用改代码。
2.3 外部数据接入:HTTP / Web Service / Socket 三套协议的取舍
原文的技术架构里列了三种接入协议:HTTP、Web Service、Socket。实际落地时,行外数据源(百融、地税、公积金、惠民征信、社保云等)多数走 HTTP 或 Web Service,报文格式以 XML 和 JSON 为主;走 Socket 的场景一般是长连接、高频小报文的数据源,比如部分实时行情类的服务。我们当时的选型经验是:能用 HTTP 尽量用 HTTP,超时控制、重试、日志都好做;WS 协议在金融行业存量系统里很常见,但解析麻烦,要处理 SOAP 头;Socket 尽量少碰,因为要自己管理连接池和心跳,接错一个包就要排查半天。
统一数据接入平台在中间做了一层协议适配,对上层只暴露标准化的查询接口。采集引擎不用关心对端是 HTTP 还是 Socket,只需要按标准消息格式发起查询并等待返回。这个设计让后面接新数据源的成本大幅降低——新数据源只需要在接入平台里配一个协议适配器,采集侧的代码一行都不用改。
外部数据接入要特别关注超时与降级策略。原文提到的“数据等待机制”就是为了避免漏数据风险而设计的。常见做法是给每个外部数据源配置独立的超时阈值,比如征信查询 8 秒、社保云查询 5 秒,超过阈值后先不等该数据源,让流程继续往下走,同时在决策结果里标记“该举证缺失”,由规则引擎决定是否走人工审批兜底。这个机制本质上是拿数据完整性换响应时效,适合对决策时效要求高的场景。
3. 流程调度与指标管理:把贷款申请串成可编排的决策流
3.1 调度平台的任务编排逻辑
调度平台在这个系统里是连接所有子模块的枢纽。它在接到授信申请后要依次做:通知采集引擎干活、等采集结果返回、推送指标给计算引擎、拿回中间变量再清洗、通知规则引擎执行、把结果回写业务系统、最后推 Kafka 异步落地。这套流程靠的不是硬编码,而是可视化流程配置。
从实践角度说,流程配置的核心是节点定义和依赖关系。贷款申请调度流程里,采集外部数据通常是多个节点并发执行——征信一个节点,社保一个节点,公积金一个节点——全部返回后再汇聚到下一个节点做清洗加工,这就是典型的“扇出-汇聚”模式。流程配置里要明确定义每个节点的超时时间、失败重试次数和失败后的处理策略(跳过还是阻断)。原文提到“合理数据调用排序可控制数据成本”,也就是说多个三方数据的调用顺序不是随便排的,要结合供应商计费方式来看:有的数据源按次计费,有的按包年不限次,优先调用包年类数据源能摊薄成本。
流程配置的 JSON 结构我们一般是这么设计的:
{ "flowId": "LOAN_APPLY_V3", "nodes": [ {"id": "start", "type": "START"}, {"id": "collect_external", "type": "COLLECT", "sources": ["CRS", "GJJ", "SBY"], "timeout": 8000, "onTimeout": "WAIT_AND_GO"}, {"id": "collect_batch", "type": "COLLECT_BATCH", "source": "EDW_TABLE", "timeout": 3000, "onTimeout": "BLOCK"}, {"id": "calc", "type": "CALC", "timeout": 5000}, {"id": "iload", "type": "RULE_EXEC", "ruleSet": "RS_LOAN_V3", "timeout": 1000}, {"id": "persist", "type": "KAFKA_SINK", "topic": "decision_loan_v3", "timeout": 500} ], "edges": [ {"from": "start", "to": "collect_external"}, {"from": "start", "to": "collect_batch"}, {"from": "collect_external", "to": "calc"}, {"from": "collect_batch", "to": "calc"}, {"from": "calc", "to": "iload"}, {"from": "iload", "to": "persist"} ] }注意collect_external节点里onTimeout配的WAIT_AND_GO,这是数据等待机制的具体实现——超时后先给一个短缓冲(比如再等 500 毫秒),期间如果有数据返回就一并带走,如果还是没有就先标记缺失继续流转。而collect_batch节点的BLOCK策略则相反,数仓指标是核心决策依据,缺失了就不往下走,宁可让请求失败返回也不能带病决策。一套流程里可以混用两种策略,关键判断依据是“这个数据丢了之后,规则引擎还能不能给出可靠的决策结果”。
3.2 指标分类与口径管理:五类指标、一千多条到底怎么组织
原文明确指出“总计在用的超过 1000 条指标”,并把它们分成五类:行内数据、行外数据、导入数据、缓存数据、申请数据。这五类的管理方式差异很大,值得展开。
行内数据来自数仓、ECIF,这类指标的历史口径稳定,按天刷新,管理重点是血缘追踪——指标是哪个表哪个字段加工出来的,必须能在指标管理页面里查到。行外数据来自外部供应商,如百融、地税、征信、公积金等,这类指标的重点是数据源切换和版本管理,因为供应商接口升级时字段含义可能变。导入数据指项目方提供的白名单类文件,比如工资贷准入企业名单、企业名单,这类数据是离线维护的,通过导入通道定时刷新。缓存数据特指由流立方加工的实时指标,比如“客户最近申请设备”和“最近申请次数”,这类指标的生命周期只在当天甚至当次请求内。申请数据是客户在申请流程里填的基本信息,年龄、性别、户籍这些。
指标管理模块对每类指标做差异化管理:行内数据按日监控加工状态,行外数据按接口维度做调用量统计,缓存数据不过度依赖——因为流立方如果出故障,实时指标会瞬间变空。指标口径要对齐,得靠一个全局的指标字典来管,每条指标的定义、来源、加工逻辑、单位、空值策略都记录在同一份配置里,采集清洗引擎加载这份字典后就能自动识别哪些指标可以加工、哪些指标缺少依赖。
衍生指标的加工逻辑,原文说得很实在:“在基础指标的基础上进行简单的四则运算”。实际例子就是“负债收入比 = 月负债 / 月收入”,这类衍生指标不需要复杂的机器学习模型,用配置就能完成。我们在指标管理里一般直接配表达式:
-- 衍生指标配置示例(伪DDL) CREATE VIEW derived_metric_loan AS SELECT cust_id, CAST(mth_debt AS DECIMAL(18,4)) / NULLIF(CAST(mth_income AS DECIMAL(18,4)), 0) AS debt_income_ratio, CASE WHEN mth_income < 0 THEN -1 ELSE mth_income END AS income_fixed FROM base_metric_loan;这里有两个细节必须留意:一是NULLIF把分母为 0 的情况转成 NULL,避免除零异常;二是对收入的负值做兜底替换,因为外部数据源偶尔会返回脏数据。如果不对这两类情况做处理,衍生指标在规则引擎里跑到一半就可能产生 NULL,而 NULL 参与规则比较的时候结果往往是“不满足条件”,很容易让一笔本来该过审的申请被拒。
3.3 指标权限控制到产品级的实现逻辑
原文里有一条值得注意的能力:“新增指标授权到产品级”。这意味着不同产品(信贷类、评分类、营销类)能看到的指标集是不同的,而不是所有产品共用一个全量指标库。这个设计的价值在于:评分卡模型和营销模型用的特征差异很大,如果所有产品都能看到全量指标,一方面有数据越权的合规风险,另一方面模型上线时会互相污染——一个产品在指标上不小心改了加工口径,其他产品全被带偏。
指标权限控制落地的常见做法是:在指标字典里加一个product_visibility字段,存一个产品编码列表;采集引擎在加工前先过滤掉当前产品不可见的指标,流程执行时也不给该产品推送无权限的指标。这个控制必须在服务端做,不能只靠前端隐藏。原文里说指标管理“负责授权给各个规则模块使用”,也就是每个规则模块(规则包)能引用的指标范围,由指标管理员统一配置,规则配置界面里看不到未被授权的指标。这样做既保证权限收敛,又让规则配置者不会配置出无法计算的规则。
4. 规则引擎:从规则包到决策流,把业务逻辑落成可配置模型
4.1 三层结构:规则包、规则组、决策流
决策平台里规则管理的组织方式可以理解成三层嵌套。最底层是规则包,负责对规则做分类管理,比如“收入核验规则包”“黑名单规则包”“反欺诈规则包”;中间层是规则组,多个规则包或多个规则可以组合成一个规则组,一个规则组表达的是一个完整的决策维度,比如“风险准入规则组”就包含了黑名单、收入核验、多头借贷等若干规则包;最上层是决策流,决策流把多个规则组串联成完整的执行单元,对应到一个具体的业务场景决策。
这个分层的价值在于复用。同一套“个人征信核验规则包”,可以在消费金融预警的决策流里用,也可以在贷中预警的决策流里用,只要在规则组里引用了同一个规则包,修改一次即全局生效。我们在实际项目中通常还会在规则包里做版本管理——一个规则包可以有多个版本,决策流里可以指定引用哪个版本。这样改规则时先在测试版本上做规则校验,通过后再切到生产版本,线上决策不用跟着规则修改而停摆。
决策流的逻辑流转一般用简化的伪代码来表达:
def execute_decision_flow(ruleset_list, feature_dict): result = {"passed": None, "score": 0, "reject_reasons": [], "hit_rules": []} for rule_group in ruleset_list: # 按顺序执行规则组 for rule in rule_group["rules"]: # 组内规则逐条执行 hit = eval_rule_condition(rule, feature_dict) if hit: action = rule["action"] result["hit_rules"].append(rule["rule_id"]) if action["type"] == "REJECT": result["passed"] = False result["reject_reasons"].append(rule.get("reason_code", "REJECT_DEFAULT")) elif action["type"] == "WEIGHT": result["score"] += action["value"] # 如果本组规则已经决出拒绝,直接短路返回,跳过后续组 if result["passed"] is False: break if result["passed"] is None: result["passed"] = result["score"] >= decision_threshold return result这里需要特别说明规则组内的短路设计:一旦某个规则触发了 REJECT 动作,整个决策流立即停止,后续规则组不再执行。原因有二:一是被拒绝的申请没有必要再消耗计算资源做评分;二是拒绝原因要尽量简洁,给业务端展示的核心拒因不宜过多,否则客户追问起来解释成本很高。但如果规则组配置的是评分型规则(WEIGHT),那就不能短路,必须把所有评分规则跑完才能汇总得分。
4.2 操作规则与决策表:两种最基本的策略模型
操作规则是策略模型最基本的配置模式,由条件和操作组成。条件描述的是“什么情况下满足要求”,比如年龄 < 25 && 征信查询次数 > 6;操作描述的是“满足要求时如何执行结果”,比如拒绝、转人工、加分、减分。操作规则比较适合表达逻辑清晰、边界明确的风控策略,比如硬性准入规则、黑名单规则。它的特点是可解释性强,每一条规则都能直接追溯到业务含义。
决策表则是“同纬度不同分段”的判断工具,特别适合评分评级。典型的例子是"按年龄分段打分":18-25 岁 50 分、26-35 岁 70 分、36-45 岁 85 分、46-55 岁 65 分、56 岁以上 40 分。这类规则如果用操作规则一条条写,会很啰嗦而且难维护;用决策表呈现就是一张二维表,业务人员自己就能改分值和阈值。
决策表的配置我放一个简化示例:
| 年龄区间 | 征信查询次数 <= 3 | 征信查询次数 4-6 | 征信查询次数 > 6 |
|---|---|---|---|
| 18-25 | 60 | 45 | 30 |
| 26-35 | 80 | 65 | 45 |
| 36-45 | 90 | 75 | 55 |
| 46-55 | 75 | 60 | 40 |
| 56+ | 55 | 40 | 25 |
决策表落地时有两个细节要特别处理:一是区间边界必须覆盖全集,也就是每一维所有取值都要落入表内某个分段,不能有“空白区”;二是分值命中的默认分支要提前定义好。我们曾经遇到过一张决策表某行某列覆盖不全,线上请求进来后决策引擎返回 NULL,业务端直接报错的情况,后来养成的习惯是所有决策表都必须配一个兜底分值(一般是全表最低分或指定默认值),并在规则校验时用边界值做全量遍历测试。这个后面避坑部分再说。
4.3 规则配置的“可解释性”设计
规则引擎在金融场景里跑得开,很大程度靠的是可解释性。原文里提到“操作规则由条件和操作组成,条件主要描述在什么情况下满足要求”,这句话背后其实要求的是每个规则节点的结果都要能记录“命中哪些规则、为什么命中、对应的动作是什么”。平台在决策结果里会返回命中规则列表和拒因编码,业务端拿到后可以直接展示给客户,或者让风控人员回溯决策路径。
我们在实际项目里一般会给每条规则配一个reason_code(拒因码)和risk_desc(风险描述),比如"HIT_BLACK_LIST", "命中内部黑名单"。决策结果里带上这两个字段,配合决策流版本号,就能精确回答“这笔申请为什么被拒”。这个设计对监管审计和模型调优都有帮助。运营人员看报表时,能按拒因码聚合统计拒笔数,快速发现某类风险是否在上升;模型团队做调优时,能通过决策流版本号还原当时用的是哪套规则,避免用新逻辑去解释老结果。
5. 排查与避坑:数据等待、指标口径、决策表边界,五个典型事故复盘
5.1 外部数据源超时,硬等导致主链路全部阻塞
现象:上线初期,某个外部数据源响应变慢,原来 2 秒返回的征信查询突然要跑 15 秒。紧接着发现所有贷款申请请求都出现耗时飙高,部分请求直接超时失败。
原因:流程配置里把外部数据采集节点设成了硬阻塞模式——这个节点不返回,后续所有节点都不执行。外部数据源一慢,整个决策链路被拖死,业务系统那边表现为“提交申请后转圈圈”。
解决:把外部数据节点的超时策略从BLOCK改成WAIT_AND_GO,设置等待阈值(我们当时设的是 5 秒),超时后先标记“该数据源缺失”并继续流程。同时在决策结果里增加缺失标记字段,规则引擎侧对“数据源缺失 + 其他准入条件边缘命中”的请求自动转人工审批。从那以后我们为每个外部数据源都建立了独立的超时和降级配置,故障数据源只影响它自己的指标,不再拖垮整条决策链路。
5.2 数仓批处理和实时计算的同一指标数值对不上
现象:同一客户在同一天通过不同入口发起申请,规则引擎跑出来的额度不一样;排查下来是“月收入”这个指标在数仓批处理口径算出来是 15000,流立方实时加工口径算出来是 12000。
原因:指标没有指定唯一来源。“月收入”在数仓里取的是代发工资流水均值,在流立方里取的是本次申请填写的收入信息,两个口径含义不同,却在指标字典里共用一个名字。规则引擎引用“月收入”时负载均衡到了两个不同的加工逻辑上。
解决:建立指标来源唯一性校验,每个指标在指标字典里强制配置source_priority(来源优先级)字段,只允许存在一个“生产环境可执行”的来源路径。规则校验工具上线前会自动扫描所有被规则引用的指标,如果发现同一指标存在多个来源,直接阻断发布。现在血泪经验是:指标字典的“同一指标只能有一个生产来源”这条规则,必须在平台层面硬性约束,不能靠团队自觉。
5.3 三方数据调用顺序不当,成本翻倍
现象:月度数据分析时发现征信查询次数环比翻了一倍,但业务量只涨了 20%。进一步查发现大量请求在“查询征信”这个节点就被拒绝了,后面的调用根本没有产生决策价值。
原因:流程配置时把征信查询放得太靠前,而且征信是按次计费的供应商。原本应该先跑内部数据规则(黑名单命中、多头借贷预警),先把明显不合格的申请拦下来,再调用付费的外部数据源。当时流程设计者图省事,把所有数据源一股脑并行采集,导致征信查询次数暴增。
解决:调整流程配置,把内部数据核验节点排在前面,把付费外部数据源全部挪到准入规则通过后再发起调用。事后平台的调度模块支持按节点配置“依赖项”——必须在哪些规则通过后才能执行本节点。这个改动上线后,征信调用量直接降了四成,决策效果没有变差。从那以后我每次看流程配置都要先问一句:“这个数据源的调用顺序是不是最优的?有没有可能先做完免费/低成本的判断再花钱查数据?”
5.4 决策表分段覆盖不全,边界值命中默认分支导致误拒
现象:某评分卡模型的通过率突然下降了 5 个百分点,查决策结果发现大量请求被一个奇怪的分值拒掉。看命中记录,分值不是真实算出来的 85 或 90,而是默认值 0。
原因:决策表里某个年龄分段(比如“56岁以上”)在某个征信查询次数段(比如“4-6次”)对应的格子是空的。决策引擎在表格里查不到对应分值,就返回了配置里的默认值(默认值为 0),规则配置里“分值低于 60 拒绝”,这批客户全部被误拒。
解决:上线前做了决策表边界校验——把所有分段边界值和分段内典型值全部遍历一遍,凡是没有对应分值的格子一律在测试阶段报错,不允许发布。同时把默认值策略从“返回 0”改成“返回该决策表全字段最小分值并标记缺失”,这样即使出现漏配,也不会因为默认 0 分产生极端误拒。这个 bug 排查花了一个下午,从那以后决策表发布的前置检查模板里,边界值遍历是被列在第一项的。
5.5 流程节点重试风暴放大数据源压力
现象:某个外部数据源短暂不可用,本来只是部分请求失败,结果演变成整站请求超时,数据库连接池被打满,最后那家数据源供应商的网关也报警了。
原因:流程配置里给采集节点加了自动重试,重试次数设置为 3,但没加退避策略。数据源不可用时,第一次调用失败后立即重试,第二次失败又立即重试,瞬间对目标系统发起大量请求;而且平台是有多实例的,每个实例都在重试,等于把故障放大了几十倍。
解决:所有重试策略统一加上指数退避,第一次重试等待 500 毫秒,第二次等待 2 秒,第三次等待 8 秒;同时对“数据源不可用”类异常只允许重试 1 次,重试失败后立即走降级路径——标记缺失并继续。重试风暴的本质是“故障转移能力缺失”,不是“重试次数不够”,这个判断后来帮我们避免了很多次类似的事故。
6. 验证动作:ABTEST 分流、规则校验、决策结果回放,上线前的最后一道防线
规则引擎迭代最怕的是改了规则包,线上一跑,某个客群的通过率骤降。靠“上线后再看效果”太危险,决策平台里提供了一套相对好用的验证组合:ABTEST 分流、规则校验和决策结果回放。
ABTEST 分流的思想很简单——同一个决策场景同时挂两个模型版本,按请求号哈希把流量分成两路,一路跑旧版本、一路跑新版本,然后用报表对比两个版本的通过率、逾期率、平均额度等核心指标。实现上不需要复杂的框架,常见的做法是用一个轻量级分流函数:
# ABTEST分流示例(伪代码) def abtest_route(request_id: str, version_a_rate: float = 0.5) -> str: # 用请求ID的哈希做稳定分流,同一请求始终进同一实验组 import hashlib digest = hashlib.md5(request_id.encode("utf-8")).hexdigest() bucket = int(digest[:8], 16) % 100 if bucket < version_a_rate * 100: return "release" # 旧版本,当前线上稳定版 else: return "candidate" # 新版本,灰度候选版这个函数要注意两个点:一是必须用请求 ID 做哈希,不能用随机数,否则同一个申请在流程里被分流到不同版本会导致决策结果不可追溯;二是实验组比例不支持运行时动态调整的话,至少要支持按请求类型和产品维度分别配比。我们当时的做法是实验配置放数据库,按产品维度维护一张实验分组表,每次请求进来先查表再路由,这样不用改代码就能调流量比例。
规则校验主要做两件事:第一件事是规则包语法检查,确保规则条件里引用的指标都存在、类型匹配、值域合法;第二件事是历史数据回放——把过去一个月的真实请求数据重新丢进新版本规则跑一遍,对比新旧版本的决策结果差异,把差异请求按“旧通过新拒绝”“旧拒绝新通过”分类输出。这里有个关键细节:回放用的必须是生产环境当时的指标数据快照,而不是重新加工后的指标,否则指标口径变化会把规则变更的效果给掩盖掉。我们搭建了一套定时任务,每天凌晨把生产环境当天的指标数据和规则执行结果同步到测试环境,供第二天的规则验证使用。
决策结果回放的意义不在于找出所有 bug,而在于给业务方一个直观的预期管理——“如果用新版本,这个月会多拒绝 0.8% 的申请,其中 5 成左右是原来的边缘通过客群”。这个数字比任何技术报告都管用,业务方看完后才敢在周会上拍板上线。
我自己养成的一个习惯是:每次改完规则包,强制自己走一遍完整的验证路径——先在测试环境跑规则校验和边界值遍历,再跑历史数据回放,最后对比 ABTEST 分流报表看差异前三项。整个过程一般不超过半个小时,但能挡住绝大多数低级错误。规则引擎的坑往往不在引擎本身,而在“改动没人复核、上线没人回放”这种流程空白上。
希望这篇拆解能帮你少踩几个坑,也祝你在搭数据决策平台时顺顺利利。
本文还有配套的精品资源,点击获取