2026-04-22 · architecture / fintech
【金融科技工程】金融科技工程全景:从支付到交易所的系统分类与读图
金融科技(FinTech)不是普通后端加一张账户表。钱的原子性、监管的硬边界、一个小数点的代价,把这个领域推进到工程强度最高的那一档。本文是【金融科技工程】25 篇的总目录与阅读地图:先交代为什么它比一般业务系统更难,再给出对账体、支付体、交易体、风控合规体四维分类,把后续 24 篇挂到骨架上,最后给出一份绿地项目的落地顺序建议。


























风控(Risk Control)是支付与交易系统里仅次于账务的”第二核心”。账务管”钱不能错”,风控管”钱不能走错地方、不能被坏人拿走、不能用于违法目的”。它不像撮合引擎那样有明确的算法答案,也不像清算那样有刚性的时间窗,但它和两者都纠缠在一起:撮合前要拦单、支付中要打分、清算后要复查。工程上它最难的不是”写几条规则”,而是在毫秒级延迟、全量流量、持续对抗的黑产环境下,把 规则、特征、画像、图、模型、决策 六件事编排成一套可回溯、可灰度、可回放的系统。
本篇面向已经搭过规则引擎、或在支付网关里写过
if amount > 10000
的读者。我们不写”什么是风控”,只谈怎么把风控做成一个引擎:它的分层、数据流、延迟预算、热更新、A/B
与冠军挑战者(Champion-Challenger)、以及一份可运行的 Go
最小实现。反欺诈模型细节、AML 报文、信用评分卡,分别留给第
20、21、22 篇。
阅读本文大约需要 40 分钟。如果你只是想抄一份架构图贴到设计评审里,可以直接跳到第四节的 SVG;如果你正在选型规则引擎或特征平台,第三节与第五节更有用;如果你在做代码实现,第十节的 Go 示例与第十三节的坑点更值得细读。
与本系列前几篇的差异:第 9 篇《支付网关》讲的是网关这一层怎么接各种上游下游、怎么做路由与幂等;本篇讲的是挂在支付网关下游的风控决策——两者在同一条链路上,但关心的问题完全不同。第 16 篇《撮合引擎》里提到的 Pre-trade Risk,本篇第 9.5 节会把它和支付风控做横向对比。
最早期的风控往往就是支付网关里一串 if:金额
> 5 万拦、夜间 > 2 点拦、同卡 1 分钟内 > 5
次拦。这种写法在两类场景下会崩溃:
当规则数量、更新频率、变更风险任何一个越过阈值,就必须把”规则”从”代码”里剥离,变成数据——这就是规则引擎存在的意义。再往上,特征、模型、决策流也逐一从代码里剥离,共同组成风控引擎。
从”几个 if”到”一套引擎”,中间要经历的不只是技术升级,还有组织升级:风控不再属于某个业务工程师顺手写的代码,而是专门团队维护的基础设施;规则变更走审批,上线走灰度,回放走仿真。把这套流程跑通,比写引擎本身更难。
行业里普遍把风控分成三层,对应交易生命周期的三个阶段:
| 层 | 位置 | 延迟预算 | 数据可见性 | 典型手段 |
|---|---|---|---|---|
| 前置(Pre-trade / Pre-payment) | 下单 / 发起支付前 | < 50 ms | 用户画像、账户状态、历史统计 | 限额、名单、KYC 状态校验 |
| 在线(Online / In-flight) | 交易处理中、授权阶段 | < 100 ms(支付)/ < 10 ms(交易所) | 全量实时特征、模型打分 | 规则 + 模型 + 决策编排 |
| 事后(Post-event) | T+0 分钟级 / T+1 批 | 秒~小时 | 落地的全量订单、外部情报 | 离线挖掘、案件调查、补处置 |
三层不是串行,而是互相回流:事后调查发现的欺诈样本,回灌成离线训练数据,训出新模型再上线到在线层;在线层累积的命中率回到前置层调整名单。
前置层的核心作用是”挡掉明显不该来的流量”,降低在线层的压力——它对延迟最敏感但规则最简单;在线层是真正的决策中心,在这里支付/交易会被通过、拒绝、挑战、转人工;事后层承担”兜底 + 学习”的职责,也是监管报送(STR、SAR)的主要数据来源。
以卡支付为例,端到端预算通常是 300 ms(用户可感知上限),从用户点”确认付款”到返回结果:
用户→收银台 → 网关 → 风控 → 发卡行 → 卡组织 → 回落
20 ms 30 ms 100 ms 80 ms 40 ms 30 ms
留给风控的大约 100 ms。这 100 ms 又要拆成:
交易所的撮合前风控(Pre-trade Risk)预算更苛刻,通常要求 < 10 ms 甚至 < 1 ms(做市商场景),这是因为撮合引擎本身的单笔处理常在微秒级,风控如果加几毫秒,队列就积压了。这种场景下几乎不能有远程 KV,只能把限额做到内存里并通过 TCP 心跳同步。
不同金融场景的延迟预算大致如下:
| 场景 | 端到端预算 | 风控预算 | 关键约束 |
|---|---|---|---|
| 扫码支付(境内) | 300 ms | 100 ms | 用户感知、发卡行响应 |
| 卡支付(跨境) | 800 ms | 150 ms | 跨境 RTT、3DS 挑战 |
| 贷款审批 | 秒级 | 500 ms | KYC、外部征信查询 |
| 证券下单 | 毫秒级 | < 10 ms | 撮合引擎吞吐 |
| 做市 / HFT | 微秒级 | < 1 ms | 本地内存、无远程调用 |
| 提现 / 转账 | 秒级 | 200 ms | 可接受延时决策 |
理解自己的延迟预算,是风控架构的起点:它决定了能不能加模型、能不能跑图、能不能做多次 KV 查询。
一套成熟的风控引擎,大致可以拆成六个互相协作的子系统。每个子系统都能独立成文章(本系列后续会展开),本节先把”分工”说清楚。
规则引擎吃两样东西:上下文(Context) 与 规则集(RuleSet),吐出命中列表与动作建议。规则的三要素是:
Rule = 条件(Condition) + 动作(Action) + 元数据(Metadata)
元数据包括规则 ID、版本、生效时间、负责人、灰度配置、优先级等。工程上要把元数据和表达式一起存成数据,不能硬编码。主流实现有两条路线:
中国互联网公司做大体量风控时,很少直接用 Drools——Rete 算法在规则多且频繁变化时内存占用高,且 Java 重;更常见的是”自研 DSL + Aviator / Groovy 表达式”,这样可以:1)控制 DSL 语法,2)做静态分析与灰度,3)把规则编译成字节码获得更好性能。
名单看起来是最简单的风控手段,但工程细节不少:
| 类型 | 作用 | 数据量典型值 | 存储 |
|---|---|---|---|
| 黑名单(Blacklist) | 直接拒 | 卡号 1000 万、设备 5000 万、IP 2000 万 | Redis + Bloom Filter |
| 白名单(Whitelist) | 直接放 | 商户、大客户、内部测试账号 | Redis + DB |
| 灰名单(Greylist) | 加强核验(OTP / 人工) | 几百万到几千万 | Redis + TTL |
| 时效名单 | 短期拦截(例如近 24 小时风险设备) | 动态 | Redis TTL |
名单的维度至少包括:卡号(BIN / PAN hash)、设备指纹、IP、手机号、商户号、收款账户、UA、邮箱 MD5、身份证 hash。大型机构会把名单按来源分层——公安部名单、央行反洗钱名单、卡组织反欺诈名单、银联天眼、自营黑样本——每一层的权重和处置动作都不一样。
画像是”主体的历史画像”,特征是”画像的投影”。在工程上这两者往往融合在同一个特征平台(Feature Platform)里。
主体维度:用户、设备、IP、商户、卡、账户、收款人、订单、会话——每一维都可以独立建库。画像字段通常分三类:
图风控用关系代替孤立主体。一笔支付涉及的主体可能只有 4 个(付款账户、收款账户、设备、商户),但把它放进全量关系图,可能一跳就关联到几千个节点——团伙的识别靠这个。典型场景:
图库选型里,Neo4j 工程成熟但单机;字节开源的 Nebula Graph 是国内大规模图风控的主流(蚂蚁自研图数据库 GeaBase 不对外);TigerGraph 商业授权、性能强;JanusGraph 依赖 HBase/Cassandra 扩展性好但延迟高。在线风控里常用的不是 OLAP 多跳查询,而是预计算的子图特征(Node2Vec、GraphSAGE 嵌入、社区 ID)落到特征平台,真正多跳查询留给事后调查。
常见模型分两类:
在线推理服务要求:QPS 数万、P99 < 30ms、模型热更新、A/B 流量分流、特征一致性(训练-推理同源)。主流实现有 TensorFlow Serving、TorchServe、NVIDIA Triton,国内大厂多自研(蚂蚁 CoGNN、字节 ByteNN、阿里 RTP)。
六个盒子的最后一环:把名单、规则、模型、图的结果编排成一个最终决策。编排器的关键职责:
阿里内部叫”决策流”(Decision Flow),蚂蚁的 AlphaRisk 里叫”策略画布”,Stripe 的 Radar 暴露给商户的则是”Rule Editor + Risk Score”。核心都是一张有向无环图,节点是”原子能力”,边是”条件”。
Drools 用 Rete 算法实现高效的多规则匹配——规则之间共享条件节点,避免每条规则独立求值。优点:
缺点也同样突出:
经验:银行、保险核心风控系统用 Drools 的不少(规则变化慢,强调合规追溯);互联网支付/交易风控几乎没人直接用,更多的是自研。
自研 DSL 的核心是一门沙箱化的表达式语言。几种主流选型:
| 语言 | 出身 | 语法 | 执行方式 | 安全性 | 热更新 |
|---|---|---|---|---|---|
| MVEL | 开源 | Java-like | 字节码/解释 | 一般(可注入) | 好 |
| Aviator | 阿里开源 | Java-like 简化 | 编译字节码 | 好(沙箱) | 好 |
| SpEL | Spring | Spring EL | 解释/编译 | 差(反射) | 好 |
| CEL | 声明式、受限 | 解释 | 极好(图灵不完备) | 好 | |
| Starlark | Bazel 衍生 | Python 子集 | 解释 | 好 | 好 |
| Groovy | Apache | Groovy | 字节码 | 差 | 好但慢 |
CEL 的特点值得多说一句:它被设计成图灵不完备——没有无界循环、无递归,保证每个表达式在常数步内返回。这让它非常适合做规则引擎的条件语言:拒绝服务攻击从语言层面被阻断,而且可以做静态开销估计。Google 的 IAM、Kubernetes Admission Webhook、Envoy 的 RBAC 全在用它。
选型经验:如果你是 Java 栈且规则复杂,选 Aviator;如果你要多语言(Go/Java/Python 都调),选 CEL;如果你是金融机构要过审计,选 Drools(生态稳定、社区认可度高)。
自研 DSL 常见的是”条件树 + 动作”的 JSON / YAML 结构:
rule:
id: R20260418-0007
version: 3
name: "夜间大额新设备支付"
priority: 80
gray:
percent: 10 # 灰度 10% 流量
condition:
and:
- "amount >= 50000"
- "hour(trade_time) between [0, 5]"
- "device.age_days < 3"
- "user.kyc_level < 2"
action:
type: challenge
method: sms_otp
ttl: 120
metadata:
owner: risk-fraud
effective: 2026-04-18T00:00:00+08:00
expire: 2026-05-18T00:00:00+08:00
tags: [night, new_device, kyc]把它编译时做三件事:
风控规则不像代码,不走”Git → CI → 发布”的慢路径,但安全红线一点不能降。工程上常见模式:
蚂蚁内部的”规则工厂”、字节的”Rangers”都内置了上面这套流程。开源领域还没有特别成熟的对标,Feast 管不了规则,Drools Workbench 的审批流太重。
当规则数量上到几千条,朴素遍历会成为瓶颈。几种常用优化:
user_id in [...]、merchant_type == 'xx'
这类等值条件建倒排,查询时只对命中的规则做完整表达式求值。经过这些优化,几千条规则的求值可以稳定在 10 ms 以内。
下图是一套典型的实时风控引擎分层架构。最上是请求入口,最下是数据源,中间是六个盒子与决策编排。
图中的两条虚线框(决策结果、Champion-Challenger)是常被架构图忽略但实战上最关键的两块:前者把”引擎给不给过”落成具体的用户体验,后者决定了”新策略能不能安全上线”。
| 类型 | 窗口 | 延迟 | 典型实现 | 典型例子 |
|---|---|---|---|---|
| 实时特征 | 秒到分钟 | ms | Flink / Storm / RisingWave + Redis | “最近 1 分钟同设备下单数” |
| 近线特征 | 分钟 | 秒 | Kafka Streams / 小批 Spark | “最近 30 分钟同 IP 金额和” |
| 离线特征 | 小时到天 | 小时 | Hive / Spark / Iceberg | “过去 30 天夜间消费占比” |
实时特征必须”写入即可读”,因为同一笔请求前脚刚进,后脚就要根据统计拦它。Flink 的 KeyedState + RocksDB + 写 Redis 是最常见的 pipeline。近线可以忍几秒延迟,用于捕捉”几分钟内的群体异常”。离线跑全量样本,沉淀长期画像。
一致性陷阱:训练用离线特征、推理用实时特征,定义口径稍有偏差就会出现”训练-推理不一致”(train-serve skew)。解决办法是把特征定义集中注册(Feast 的 feature view、字节 ByteNN 的 feature catalog),同一份 SQL/DSL 两边分别编译成 Flink job 和 Spark job,保证语义一致。
滑动窗口(sliding window)在 Flink 里常见写法:
DataStream<Txn> txns = env.addSource(kafkaSource);
txns.keyBy(Txn::getDeviceId)
.window(SlidingProcessingTimeWindows.of(Time.minutes(1), Time.seconds(1)))
.aggregate(new CountAgg())
.addSink(new RedisSink("feat:device:{}:txn_cnt_1m"));关键工程点:
特征越多,命名越重要。大厂普遍采用”维度_指标_窗口_聚合”四段式:
user_txn_amt_1m_sum # 用户 1 分钟内交易金额总和
device_login_cnt_1h_uniq # 设备 1 小时内登录次数(去重)
merchant_chargeback_30d_rate # 商户 30 天内拒付率
ip_user_cnt_24h_distinct # IP 24 小时内关联用户数(distinct)
每个特征都有元数据:定义 SQL、类型、默认值、来源上游作业、owner、使用方。没有注册到元数据系统的特征不能在规则里引用——这是防止”幽灵特征”的铁律。
特征下线同样重要。Flink 作业数会随特征数线性增加,资源成本失控。做法是:每月扫描特征使用情况,连续 60 天零引用的特征自动停掉对应 Flink 作业,释放资源。
画像不是一张大宽表,而是一组按主体维度切分的独立存储。设计要点:
典型支付风控用到的画像维度组合:
(用户, 设备, IP, 卡, 商户, 收款方, 会话, 订单)
八个主体两两之间都能形成”关系特征”:用户-设备新鲜度、IP-卡地理距离、商户-卡历史交易数……这些关系特征构成了规则表达式里最常用的字段。
实际画像存储示例(用户维度):
CREATE TABLE profile_user (
user_id BIGINT PRIMARY KEY,
register_ts BIGINT,
kyc_level TINYINT, -- 0 未实名 1 L1 2 L2 3 L3
risk_tag VARCHAR(64), -- 标签集合
device_count_30d INT,
ip_count_30d INT,
txn_cnt_30d INT,
txn_amt_30d DECIMAL(20, 4),
night_ratio_30d DECIMAL(5, 4), -- 夜间交易占比
community_id BIGINT, -- 图社区 ID
embedding BLOB, -- 行为序列 128 维向量
updated_ts BIGINT,
version INT,
INDEX idx_community (community_id),
INDEX idx_updated (updated_ts)
);这张表并不会真的建在 MySQL 里——数据量过亿后它通常物化在 HBase/Cassandra 或自研 KV 里。建表语句的价值在于把字段口径定死,所有上游 Flink/Spark 作业按这个口径生成。
图风控真正擅长的不是”多跳查询”,而是团伙识别与关联穿透。几个经典模式:
选型建议:
在线层一般不跑多跳查询(延迟不可控),而是离线预计算子图特征(节点嵌入、社区 ID、1/2 跳邻居聚合值)落到特征平台,在线层只做 KV 查询。
一个典型的洗钱团伙识别流程:
这一流程通常是”离线每日一跑 + 在线 KV 查询”模式,在线不做任何图遍历。唯一例外是实时关联分析场景(例如收款方刚刚被标黑,需要实时穿透到过去 1 小时内所有向它转账的账户),这时要么用 Flink CEP 做流式关联,要么用 TigerGraph / Nebula 的实时子图查询能力。
模型细节留给第 20 篇,这里只讲模型在引擎里的位置。
一个典型的风控团队,模型演进大致经历四个阶段:
国内一线机构大多处于阶段 4(支付宝 AlphaRisk、腾讯钱龙、京东 ZGuard),二线多在阶段 3,中小机构在阶段 2。
模型在风控里真正难的不是建模,是线上线下一致性。三条铁律:
金融风控的合规要求远高于推荐、广告。监管常要求能回答”这笔交易为什么被拒”:
黑产不断进化,模型性能会不断衰减。工程上要盯住两个指标:
常见重训节奏:GBDT 每周或每月;DNN/GNN 2–4 周;关键高风险模型每日增量 + 每周全量。
决策流是一张 DAG:节点是原子能力(一组规则、一个模型、一次查询),边是条件。调度器负责并行、短路、降级、落 trace。
flowchart LR
A[入口] --> B{黑名单?}
B -- 命中 --> Z[拒绝]
B -- 未命中 --> C[并行]
C --> D[规则组-限额]
C --> E[规则组-行为]
C --> F[GBDT 模型]
C --> G[GNN 图模型]
D & E & F & G --> H[融合决策]
H --> I{score}
I -- ">0.9" --> Z
I -- "0.6~0.9" --> J[OTP 挑战]
I -- "0.3~0.6" --> K[延时 + 观察]
I -- "<0.3" --> P[通过]
融合策略(Fusion)可以是加权和、OR 规则、或另一个 meta-model。出于可解释性,很多金融机构要求主决策必须是规则可读的——模型分作为一个输入,最终”YES/NO”由规则判定。
新策略上线不能直接替换旧策略,因为风险巨大:万一误杀率暴涨,GMV 立刻掉。Champion-Challenger 模式:
工程上的关键点:Champion 和 Challenger 必须用同一份上下文(相同的特征快照),否则对比不可信。
A/B 不只用于”选更好的模型”,也用于测策略(挑战 vs 拒绝)、测文案(OTP 提示怎么写用户更愿意通过)、测阈值(分数 0.8 还是 0.85 拦)。每个实验要:
在并行化调度之上,决策编排通常还要做几件事:
可以把这一层类比成”微服务编排 + 实时性要求极端化”。国内大厂的决策编排引擎大多参考开源工作流引擎(如 Conductor、Argo)但重新实现,以满足 P99 < 100 ms 的硬性要求。开源工具如 Temporal 的吞吐与延迟暂不能满足生产风控要求,可以作为异步批任务使用。
AlphaRisk 是支付宝的实时风控大脑,对外公开资料里强调几点:
AlphaRisk 公开披露的四代演进可以粗略概括为:
每一代的共性是”特征-模型-策略”三件套越来越自动化。
芝麻信用是 AlphaRisk 生态里的信用评分子系统,给出 350–950 的芝麻分,用于免押、贷款、信用卡初审。其特征覆盖身份、履约、行为、人脉、资产五个维度。详细机制留给第 22 篇。
Stripe Radar 是国外最成熟的支付反欺诈服务之一,特点:
Radar 在面向商户暴露能力时做了一个值得借鉴的设计:风险分 + 规则 双层。风险分由 Stripe 负责维护与迭代,商户无权访问模型细节,但可以基于分数 + 其他字段写自己的规则。这套分层让 “Stripe 的专家经验” 与 “商户的业务经验” 解耦,也规避了大量跨商户合规难题。
PayPal 是最早大规模使用机器学习做反欺诈的支付机构之一,1999 年就有 IGOR 系统。关键经验:
交易所的风控与支付风控有本质差异:延迟更苛刻(< 10 ms)、规则更简单(主要是限额、自成交防护、过于偏离市价的订单)、没有模型打分(时间根本不够)。CME、纳斯达克以及中国期货交易所的 Pre-trade Risk 都是纯规则、内存化、与撮合引擎同机部署的。
然而,交易所的风控承担了撮合引擎”最后一公里”的合规防护:
这些规则在第 15、16 篇讲撮合时已粗略提及,本篇不再展开。
下面的代码实现了:条件树解析、特征获取接口、规则匹配、决策编排。不到 200 行,但结构和生产系统同构——读懂它再看大厂代码会顺畅很多。
package riskengine
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
)
// FeatureFetcher 从特征平台/缓存里拿特征
type FeatureFetcher interface {
Get(ctx context.Context, keys []string) (map[string]any, error)
}
// Action 决策动作
type Action string
const (
ActionPass Action = "pass"
ActionReject Action = "reject"
ActionChallenge Action = "challenge"
ActionReview Action = "review"
)
// Rule 规则定义
type Rule struct {
ID string `json:"id"`
Version int `json:"version"`
Name string `json:"name"`
Priority int `json:"priority"`
GrayPct int `json:"gray_pct"` // 灰度百分比
Condition ConditionNode `json:"condition"`
Action Action `json:"action"`
Meta map[string]any `json:"meta"`
}
// ConditionNode 条件树节点
type ConditionNode struct {
Op string `json:"op"` // and / or / gt / gte / lt / lte / eq / in
Field string `json:"field"` // 叶子节点引用的特征
Value any `json:"value"` // 叶子节点比较值
Children []ConditionNode `json:"children"` // 组合节点
}
// Decision 决策输出
type Decision struct {
Action Action `json:"action"`
Hits []string `json:"hits"` // 命中规则 ID
Score float64 `json:"score"` // 模型分(若有)
TraceID string `json:"trace_id"`
Features map[string]any `json:"features"`
TookMS int64 `json:"took_ms"`
}
// Engine 规则引擎
type Engine struct {
mu sync.RWMutex
rules []Rule
feats FeatureFetcher
hash func(string) uint32 // 用于灰度分桶
}
func NewEngine(f FeatureFetcher, hash func(string) uint32) *Engine {
return &Engine{feats: f, hash: hash}
}
// UpdateRules 热更新:一次性替换全部规则
func (e *Engine) UpdateRules(rs []Rule) {
e.mu.Lock()
defer e.mu.Unlock()
e.rules = rs
}
// Evaluate 核心入口
func (e *Engine) Evaluate(ctx context.Context, traceID string, input map[string]any) (*Decision, error) {
start := time.Now()
// 1) 收集所有规则要用的特征字段
e.mu.RLock()
rules := e.rules
e.mu.RUnlock()
keys := collectFeatureKeys(rules)
// 2) 一次批量拉取特征(让平台侧合并 IO)
feats, err := e.feats.Get(ctx, keys)
if err != nil {
// 降级:特征拿不到时只用 input 里的原生字段
feats = map[string]any{}
}
for k, v := range input {
feats[k] = v
}
// 3) 按优先级从高到低评估
var hits []string
finalAction := ActionPass
for _, r := range rules {
// 灰度控制:hash(traceID+ruleID) 落入灰度区间才执行
if r.GrayPct > 0 && int(e.hash(traceID+r.ID)%100) >= r.GrayPct {
continue
}
ok, err := evalNode(r.Condition, feats)
if err != nil {
continue
}
if ok {
hits = append(hits, r.ID)
// 动作优先级:Reject > Review > Challenge > Pass
finalAction = mergeAction(finalAction, r.Action)
if finalAction == ActionReject {
break // 短路
}
}
}
return &Decision{
Action: finalAction,
Hits: hits,
TraceID: traceID,
Features: feats,
TookMS: time.Since(start).Milliseconds(),
}, nil
}
func mergeAction(cur, in Action) Action {
order := map[Action]int{
ActionPass: 0, ActionChallenge: 1, ActionReview: 2, ActionReject: 3,
}
if order[in] > order[cur] {
return in
}
return cur
}
func collectFeatureKeys(rs []Rule) []string {
seen := map[string]struct{}{}
var walk func(ConditionNode)
walk = func(n ConditionNode) {
if n.Field != "" {
seen[n.Field] = struct{}{}
}
for _, c := range n.Children {
walk(c)
}
}
for _, r := range rs {
walk(r.Condition)
}
out := make([]string, 0, len(seen))
for k := range seen {
out = append(out, k)
}
return out
}
func evalNode(n ConditionNode, feats map[string]any) (bool, error) {
switch n.Op {
case "and":
for _, c := range n.Children {
ok, err := evalNode(c, feats)
if err != nil || !ok {
return false, err
}
}
return true, nil
case "or":
for _, c := range n.Children {
ok, err := evalNode(c, feats)
if err == nil && ok {
return true, nil
}
}
return false, nil
case "gt", "gte", "lt", "lte", "eq":
lv, ok := toFloat(feats[n.Field])
rv, ok2 := toFloat(n.Value)
if !ok || !ok2 {
return false, fmt.Errorf("non-numeric compare on %s", n.Field)
}
switch n.Op {
case "gt": return lv > rv, nil
case "gte": return lv >= rv, nil
case "lt": return lv < rv, nil
case "lte": return lv <= rv, nil
case "eq": return lv == rv, nil
}
case "in":
arr, ok := n.Value.([]any)
if !ok { return false, nil }
for _, x := range arr {
if fmt.Sprintf("%v", x) == fmt.Sprintf("%v", feats[n.Field]) {
return true, nil
}
}
}
return false, nil
}
func toFloat(v any) (float64, bool) {
switch x := v.(type) {
case float64: return x, true
case float32: return float64(x), true
case int: return float64(x), true
case int64: return float64(x), true
case json.Number:
f, err := x.Float64()
return f, err == nil
}
return 0, false
}这段代码里有几处写生产代码时必须补齐的地方:
决策结果不只是 “通过 / 拒绝” 两个枚举值——落到用户界面上,它决定了转化率、客诉率和风险损失的三角平衡。
| 动作 | 含义 | 适用场景 | 用户感知 |
|---|---|---|---|
| 通过(Pass) | 正常放行 | 绝大多数低风险交易 | 无感 |
| 拒绝(Reject) | 终止交易 | 命中黑名单、极高分 | “交易失败,请联系客服” |
| 挑战(Challenge) | 追加验证 | 中高风险 | 短信 OTP / 指纹 / 3DS / 刷脸 |
| 延时(Delay) | 异步风控 | 高金额但非实时场景 | “处理中,稍后通知” |
| 人工审核(Review) | 转人工 | 极高风险且无法机器判断 | “交易受限,等待审核” |
工程要点:
对外错误码不能暴露风控逻辑。例如命中”设备指纹欺诈团伙”,对用户提示”当前网络环境异常,请更换网络后重试”——这既不泄露信息又给了用户一条可操作路径。内部错误码则必须 1:1 对应规则 ID + 模型子分支,供 CSR(客服)反查。
大厂风控团队通常会沉淀一套”错误码词典”,每个错误码三列:内部原因、用户话术、客服话术。任何新规则上线前必须挂靠已有错误码或申请新码。
被误拦的用户会走客服或 App 内申诉通道。申诉命中率高的规则会反向推高业务指标下滑——风控团队的 OKR 里必须同时写 “欺诈损失率” 与 “误拦申诉率” 两项。典型闭环:
评价一套风控引擎不能只看”拦了多少欺诈”,更要看综合业务影响。典型指标组:
| 指标 | 口径 | 目标方向 |
|---|---|---|
| 欺诈损失率 | 欺诈金额 / GMV | 越低越好 |
| 误拦率 | 误拦订单数 / 全部订单 | 越低越好 |
| 召回率 | 成功拦截的欺诈 / 全部欺诈 | 越高越好 |
| 精确率 | 成功拦截的欺诈 / 所有拦截 | 越高越好 |
| 挑战通过率 | OTP 通过数 / 挑战数 | 上升说明挑战偏严 |
| 转人工比例 | 转人工数 / 全部决策 | 控制在 0.1%–1% |
| 人工审核 TAT | 转人工到处理完成耗时 | 越低越好 |
| 申诉平反率 | 申诉成功数 / 申诉总数 | < 10% 合格 |
这些指标要日报 + 周报,异常波动要回溯到规则 / 模型版本。OKR 里最常见的两个顶层指标是 “欺诈损失率 < X bp” 和 “误拦申诉率 < Y%”,二者对立制衡。
金融场景的风控决策必须可回放——监管、内审、业务复盘三方都会要求”为什么 2026-04-20 14:35:22 这笔交易被拦了”。
trace_id, ts, user_id, order_id, biz_type,
ruleset_version, model_version, feature_snapshot_id,
hit_rules: [id, version, action],
model_score, model_subscores,
graph_features_used,
final_action, final_reason_code,
challenge_result, human_review_result,
downstream_latency_ms
其中 feature_snapshot_id 是关键:特征值在写 trace 时必须快照,不能只存 “key”。否则 24 小时后 Redis 里的特征已经变了,trace 无法重现当时决策。
回放系统的作用有三:
回放要解决的核心是”时间还原”:特征必须是当时的快照而不是当前值。工程上两种方式:
大厂做法多是两者结合:核心特征走快照,边缘特征走事件溯源。
境内支付与持牌机构需要按期向央行、银保监、外管报送数据。风控 trace 是报送的重要原料之一:
因此 trace 存储必须满足:至少 5 年可访问(部分监管要求 10 年)、不可篡改(可用追加日志 + 哈希链,或直接上区块链存证)、可基于规则版本和模型版本精准回放。
| 场景 | 规则引擎 | 特征平台 | 图 | 模型服务 |
|---|---|---|---|---|
| 日均单量 < 100 万 | Easy Rules / 自研 DSL | Redis + 直写 | Neo4j | 模型内嵌 Go/Java |
| 100 万 – 1 亿 | Aviator + 自研 | Feast + Redis | Nebula / TuGraph | Triton / 自研 gRPC |
| > 1 亿(大厂级) | 自研 DSL + 字节码 | 自研(字节 / 阿里 / 蚂蚁路线) | Nebula / 自研图库 | 自研(ByteNN / 类 AlphaRisk) |
| 银行 / 证券合规重 | Drools / 商业 BRMS | 厂商方案或自研 | TigerGraph / JanusGraph | 自研 + 白盒模型 |
如果把这几篇放在一起看,就能理解为什么大厂的”风险与合规中台”往往是几百到上千人的团队:规则、特征、模型、图、KYC、AML、信用、对账、监管报送——九件事互相耦合,任何一块薄弱都会被黑产或监管放大。
上一篇:《证券登记结算:中证登、DTCC、Euroclear、T+1、DvP》
把当前热点继续串成多页阅读,而不是停在单篇消费。
2026-04-22 · architecture / fintech
金融科技(FinTech)不是普通后端加一张账户表。钱的原子性、监管的硬边界、一个小数点的代价,把这个领域推进到工程强度最高的那一档。本文是【金融科技工程】25 篇的总目录与阅读地图:先交代为什么它比一般业务系统更难,再给出对账体、支付体、交易体、风控合规体四维分类,把后续 24 篇挂到骨架上,最后给出一份绿地项目的落地顺序建议。
2026-04-22 · architecture / fintech
系统梳理反欺诈工程:欺诈类型、特征工程、模型演进(规则到 GNN)、类别不平衡、图风控、实时决策、对抗性与可解释性,附 Python + NetworkX + LightGBM 简化团伙识别与欺诈评分示例。
2026-04-22 · architecture / fintech
从金融本质、分层架构到文件格式、匹配算法、差错处理与调账流程,系统讲解对账系统的工程落地,包含 Python/Go 示例、Mermaid 状态机与大型平台十亿级对账方案。
2026-04-22 · architecture / fintech
面向中国工程团队的金融科技系列。从账务底盘、支付、清结算、交易所、风控合规到可靠性与灾备,中国与全球视角并举,讲清楚金融系统在工程落地中的真实挑战。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。