
支付风控规则这个东西外行听着像玄学内行做起来全是脏活累活。我带过几支风控小组最常见的画面是产品经理拿着一条客诉冲进来说用户正常付款被拦了赶紧把规则关掉而风控同学打开后台发现这条规则上线才三天拦了八千笔其中七千多笔确实有问题剩下那几百笔才是误伤。关还是不关这个判断力就是支付风控规则这门手艺的核心。这篇内容不打算讲什么高大上的模型架构就聊规则本身——从一笔请求进来到决策返回中间那几百毫秒里到底发生了什么规则怎么设计、阈值怎么定、上线之后怎么盯、误杀之后怎么查。适合刚接手风控策略的同学、做支付业务的研发、以及被风控拦过却又想搞明白为什么的产品同学。看完你至少能自己动手配一条不丢人的规则并且知道它上线之后会怎么咬人。1. 支付风控规则在整条链路上的介入位置1.1 风控不是支付那一下才开始的新手最容易犯的错是把支付风控规则理解成支付接口前面那道闸机。实际上闸机只是最后一道前面还有好几道门。一个完整的支付链路里风控至少在这些节点会介入注册环节判断是不是批量养号、登录环节判断是不是盗号、绑卡环节判断这张卡是不是和几十个账号关联、下单环节判断商品和收货地址有没有异常、支付环节判断这次付款的资金流向和频次、以及支付成功之后的退款和提现环节。为什么要把规则铺得这么散因为支付的本质是资金转移一旦钱出去了追回来的成本极高。行业里有个粗略的说法事前拦截一笔欺诈的成本是几毛钱事中人工审核是几块钱事后追款是几十块甚至收不回来。所以规则前置是第一原则能在注册时拦掉的账号就不要留到支付时才发现。我见过一些团队把所有资源都堆在支付那一瞬间结果就是规则越堆越多、响应时间越来越长、误杀越来越重最后变成宁可错杀一千的粗暴状态用户体验和业务量一起掉。提示如果你刚接手一条存量风控链路先别急着加规则先把现有规则按介入节点列一张表标清楚每条规则在哪个节点生效、拦了什么、拦了多少。这一步能帮你快速看出规则分布是不是畸形。1.2 规则要回答的三个本质问题不管规则写得多花哨它归根到底只在回答三个问题这次操作是不是账号本人在做、是不是本人真实意愿、这笔钱本身干不干净。这三个问题对应的风险类型完全不同处理手段也不一样。是不是本人对应的是账号盗用类风险。判断依据通常是设备指纹是否更换、登录地是否突变、操作习惯是否偏离历史。这类规则的特点是宁可敏感一点因为盗号损失是直接的而且用户会主动来找你误杀了打个电话就能解。是不是本人意愿对应的是被诱导类风险比如用户被骗着去付款、被诱导充值。这类最难做因为从系统视角看所有行为都是用户自己点的没有任何技术异常。规则能抓的往往是间接信号收款方是不是新注册、是不是短时间内收到大量来自不同用户的付款、金额是不是有聚集特征。钱干不干净对应的是资金链风险涉及洗钱、跑分、赌博充值这类。这类看的不是单笔而是整体资金网络的形态账号之间有没有环形转账、收款账号是不是快进快出、资金留存时间是不是特别短。把这三个问题分开之后你会发现很多规则冲突迎刃而解。比如一条规则为了抓盗号把设备变更判成高风险另一条规则为了抓骗局把新设备直接拒绝两条规则叠在一起就会误伤大量换手机的正常用户。给每条规则标注它服务的是哪个问题、对应的调性是什么是规则治理的第一步。2. 四类规则骨架名单、频次、阈值、关系2.1 名单类规则上手最快腐烂也最快名单类规则就是黑名单、白名单、灰名单。黑名单直接拒绝白名单直接放过灰名单转人工或者要求二次验证。这类规则写起来最简单一条 if 就完事但它是最容易腐坏的一类。我见过一个团队的黑名单跑了一年多没清理里面躺着三十多万条记录其中大量是当年某个临时活动误伤进来的正常用户。这些人后来一直用不了支付投诉到客服客服也不知道为什么。更麻烦的是黑名单的写入往往没有严格的准入标准某个运营同学觉得这个人可疑就加进去加完就没人管了。比较健康的做法是给名单加上时间属性和来源属性。每条记录都要有加入时间、加入原因、加入人、过期时间。永久黑名单只留给证据确凿的场景比如确认的欺诈账号、司法协查名单。其余全部设定有效期到期自动降级或者进入复核队列。注意白名单是最危险的东西。一旦业务方发现加白名单就能绕过风控各种关系户就会涌进来。白名单必须走审批并且定期审查存续必要性我习惯每隔一个季度就把白名单全量导出逐条问这条还需要吗。2.2 频次类规则滑动窗口的坑比想象中多频次类规则是风控的主力比如同一设备一分钟内发起超过5笔支付同一张卡一小时内被超过3个账号使用同一IP十分钟内注册超过10个账号。听起来简单实现起来全是细节。第一个坑是窗口的定义。固定窗口会带来边界问题如果限制是每分钟5笔用户在 00:59 发5笔、01:01 又发5笔固定窗口会认为两分钟各5笔都合规但实际上两秒钟内发生了10笔。所以支付场景基本都要用滑动窗口用 Redis 的有序集合或者 Flink 的状态窗口来实现按事件时间戳精确计数。第二个坑是维度的选择。同一个10分钟内5笔的限制按设备算、按账号算、按银行卡算、按IP算抓到的风险完全不同。盗号团伙通常换账号不换设备跑分团伙通常换设备不换卡薅羊毛的通常换一切但集中在同一个收货地址。所以实际生产中一条频次规则往往是多个维度同时计数任一维度超限即触发。第三个坑是计数的时间基准。用客户端上报的时间还是服务端接收的时间必须是服务端时间。客户端时间可以被篡改而且存在时区和时钟漂移问题。我见过一个案例攻击者把设备时间往后调导致所有请求的服务端时间戳看起来都落在同一毫秒反而绕过了滑动窗口的判断逻辑。-- 计算某设备在滑动一小时内的独立账号数用于团伙聚集判断 SELECT device_id, COUNT(DISTINCT uid) AS uid_cnt_1h FROM dwd_payment_event WHERE event_time NOW() - INTERVAL 1 HOUR GROUP BY device_id HAVING COUNT(DISTINCT uid) 3;2.3 阈值类规则抓的是金额和比例的异常形态阈值类规则看的是单次操作的数值特征比如金额异常、折扣比例异常、余额变动异常。这类规则的关键不是多大算大而是相对于谁算大。同样是支付五千块对于一个日均消费三百的用户是异常对于一个每月固定交房租的用户是正常。所以阈值类规则几乎都要带个性化基线。常见的做法是给每个用户算一个历史金额的分位数比如取过去九十天支付金额的 P95 作为基线超过基线三倍才触发。这样既能抓住突发的异常大额又不会因为用户换了消费场景就误伤。还有一类阈值规则针对的是比例而非绝对值比如优惠抵扣金额占订单金额比例超过90%退款金额超过原订单金额。这类规则对付的是薅羊毛和退款欺诈数值型阈值在这种场景下比频次更有效因为攻击者可以控制频次但很难控制比例。2.4 关系类规则从单点判断升级到团伙识别前面三类规则看的都是单个账号、单台设备、单张卡。但真实的欺诈很少是孤立的背后几乎都是一个团伙共享设备、共享银行卡、共享收款账号、共享收货地址。关系类规则就是把账号、设备、卡、手机号、地址这些实体当成图的节点把它们之间的关联当成边然后在这个图上找异常结构。最基础的关系规则是共用判断同一个设备登录过几个账号、同一张卡被几个账号绑定、同一个收货地址收到过多少个不同账号的订单。进阶一点的是找环比如 A 转账给 BB 转账给 CC 又转回 A这种环形资金流是很典型的洗钱特征。再进阶就是社区发现用图算法把明显抱团的节点聚成一个簇整簇一起处理效率比一条条规则去匹配高得多。关系类规则的计算成本明显高于前三类通常不会放在实时链路上而是离线跑出结果把高风险的关系标签写回用户画像实时规则再去读这些标签。这是一种典型的离线算关系、在线用标签的分工。3. 特征字段与算子一份能直接落地的规则配置结构3.1 特征字段的四个来源写规则之前得先想清楚规则能读哪些数据。我把风控规则的特征字段分成四类来源。第一类是请求自带字段也就是这次支付请求里直接带着的东西金额、币种、商户号、商品类目、支付方式、收款账号。这类字段最实时、最准确但信息量有限单看这些几乎判断不出什么。第二类是实时聚合字段需要风控系统自己算比如设备近一小时支付笔数、账号近十分钟登录失败次数、收款方近一天收款笔数。这类字段是规则的主力计算逻辑通常放在流处理任务里结果写进低延迟的存储供规则引擎读取。第三类是画像标签字段属于离线产出比如账号注册天数、历史成功支付笔数、历史投诉次数、设备是否被标记为模拟器。这类字段更新频率低但信息密度高是判断这个账号是不是新号这类问题的关键依据。第四类是外部数据字段比如银行卡 BIN 对应的发卡行和卡种、手机号归属地、IP 归属地。这类数据通常通过第三方服务或者本地库获取用于做交叉验证比如账号注册地和本次支付IP归属地相隔三个省就是一个值得关注的信号。这四类字段在规则里的用法差异很大配置时最好在字段命名上做区分比如实时聚合用agg_前缀画像标签用tag_前缀一眼就能看出这条规则依赖的是哪类数据排查问题时也能快速定位是哪个数据源出了岔子。3.2 算子设计为什么坚持原子算子而不是随便写脚本我见过两种极端的规则引擎实现。一种是允许策略同学直接写 Java 或 Python 脚本灵活度拉满但结果是规则不可读、不可审计、性能不可控同一条逻辑十个人写出十种。另一种是只给下拉框只能选大于小于等于安全但表达力太弱稍微复杂的逻辑就得开发介入。比较舒服的做法是原子算子加组合。原子算子是一组固定的、语义明确的判断函数比如时间窗口计数、分位数比较、集合包含、字符串相似度、地理位置距离。策略同学通过组合这些算子来表达逻辑既能覆盖大多数场景又保证了每条规则的行为可预测、可审计。规则配置本身建议用 JSON 或 YAML 这种结构化格式配合版本管理。好处是规则变更可以被 review、可以回滚、可以 diff出了事故能快速定位是哪次改动引入的。这一点在多人协作的时候尤其重要我经历过一次线上事故最后查出来是某条规则的阈值被人从500改成了50没有走审批没有任何记录靠着一层层翻日志才找到。{ rule_id: R_PAY_0017, rule_name: 同设备短时多账号聚集支付, scene: payment_checkout, version: v3, priority: 300, window: 10m, conditions: { and: [ { field: agg_device_distinct_uid_1h, op: gte, value: 3 }, { field: order_amount, op: between, value: [100, 5000] }, { field: tag_user_reg_days, op: lt, value: 7 }, { field: device_is_emulator, op: eq, value: false } ] }, action: { decision: review, risk_level: medium, ttl: 7d }, owner: risk_team_a, created_at: 2024-06-01 }上面这段配置里priority决定规则的执行顺序window声明时间窗口conditions是条件树action是命中后的动作。把动作和条件分开是为了支持影子模式——只记录命中不实际拦截这个后面会详细讲。3.3 规则优先级与命中顺序的设计规则一多冲突就来了。五条规则同时命中有的说拒绝有的说通过听谁的这时候优先级就是唯一的裁判。我的习惯是按动作的严重程度倒序排直接拒绝的最高要求二次验证的次之转人工审核再次打标观察最低。同一级别里再按规则的精确度排证据越硬的越靠前。顺序为什么重要因为有些规则一旦命中后面的规则根本不需要再算直接短路返回这能省下大量计算时间。比如一条确定性的黑名单规则命中就没必要再跑二十条实时聚合规则了。但也有些场景需要全量计算比如风控报表需要统计每条规则的独立命中量这时候就得把短路逻辑关掉。提示短路优化和全量统计是有冲突的。我的做法是设置一个采样开关主链路走短路保证低延迟同时按1%的采样率跑全量计算用采样数据来评估每条规则的独立贡献两边都不耽误。4. 阈值怎么定从历史分布里切而不是拍脑袋4.1 先把分布画出来再说话新手定阈值最常见的动作是看看昨天线上数据觉得5笔好像差不多然后写个5。这个动作的问题在于你根本不知道那个字段的真实分布长什么样。可能95%的用户这个字段是0到15%是2到3而攻击者是50起步那你定5还是定8其实都无所谓都有效。但如果分布是连续渐变的那定5和定8的差别可能就是几万笔误杀。所以定阈值的第一步永远是拉分布。把过去三十天的该字段值全部捞出来按正常用户和确认风险用户两个群体分别画直方图或者累计分布曲线。你会看到两种典型形态一种是两个群体有明显间隔中间有一块空白区那阈值就取在空白区里怎么取都行另一种是两个群体重叠严重怎么切都会误伤这种字段本身就不适合做单点阈值得换字段或者组合字段。-- 分别看正常与风险群体的分布找阈值切点 SELECT is_fraud, approx_percentile(amount, 0.5) AS p50, approx_percentile(amount, 0.9) AS p90, approx_percentile(amount, 0.99) AS p99, MAX(amount) AS max_amount FROM dws_payment_feature WHERE dt BETWEEN 2024-05-01 AND 2024-05-31 GROUP BY is_fraud;4.2 用分位数和区分度指标确定初值有了分布接下来是选切点。常用两个参考量分位数和区分度。分位数的作用是控制影响面。如果你希望这条规则最多影响0.5%的正常用户那就去看正常群体分布的P99.5把阈值定在那附近。这样从统计意义上误伤率上限就有了个大概的锚。注意这里是大概因为分布会漂移今天合理的阈值过两个月可能就不合理了。区分度指标衡量的是这个切点把两个群体分开的能力。直观理解就是切点以下有多少是正常用户、多少是风险用户切点以上反过来。理想情况下我们希望切点以上风险用户的浓度远高于切点以下。这个浓度差越大规则的性价比越高。定阈值的时候可以在多个候选切点上算一遍选浓度差最大的那个。定完初值还有一个必须做的事小流量验证。哪怕统计学上算得很漂亮真实流量里总会有你没想到的情况。所以初版阈值不要直接全量生效先影子跑几天看真实命中量和真实误杀反馈再决定是收紧还是放松。4.3 阈值上线后必须盯的三条曲线阈值不是定完就完了它需要持续被监控。我习惯盯三条曲线。第一条是命中量曲线。正常情况下它应该是平稳的或者有规律波动的比如白天高晚上低。如果某天突然翻倍要么是有新的攻击手法来了要么是上游数据出了问题导致字段值整体偏移两种情况都需要立刻查。第二条是误杀率曲线。误杀率的获取通常要等客诉所以有滞后。但可以通过命中规则的账号在后续七天内的自证行为来近似估计比如命中后用户完成了一次人脸验证并且继续正常使用那就大概率是误杀。这条曲线缓慢上升就是阈值该放松的信号。第三条是字段分布漂移曲线。监控阈值字段本身的分布变化如果分布整体右移了说明用户行为在变化原来的阈值相对就变严了误杀会自然上升。这条曲线往往能提前于误杀率曲线发出预警。5. 误杀投诉的排查链路一次完整的回溯5.1 先把决策快照捞出来排查误杀最忌讳的就是凭印象猜。线上环境千变万化你必须能看到当时到底发生了什么。所以风控系统必须保存决策快照快照里至少要包含请求时间、命中的规则ID和版本、每条命中规则读取到的字段实际值、最终的决策动作、以及本次请求的关键标识设备号、账号、订单号。没有快照的系统排查起来就是噩梦。我经历过一个早期的系统只记录了命中规则X字段值一个都没存用户投诉来说自己正常付款被拦我们只能看着规则X的条件发呆完全不知道当时是哪个字段踩线了。后来花了两个月补齐快照才把排查效率提上来。有了快照排查的第一步就很简单拿用户的账号或订单号去捞快照看一眼最终决策是哪几条规则共同作用出来的。通常情况是用户看到的是付款失败但背后其实是两三条规则叠加命中的结果单独任何一条都不足以拒绝叠加起来才拒绝。5.2 逐条验证这条规则为什么命中捞出快照之后逐条看每条命中规则的字段值。这里最常发现三类问题。第一类是字段值本身就不对。比如设备指纹因为用户升级了系统被重新生成导致系统认为换设备了。这类问题属于数据质量问题不是规则逻辑问题修的是上游采集。第二类是字段值对但规则组合的语义有偏差。比如新设备和新账号两个条件单独看都合理但组合起来在大促期间会误伤大量真实新用户因为大促本来就是拉新的时候。这类问题是规则设计时缺少场景上下文修的是规则本身加个条件排除大促场景就行。第三类是字段值对、规则也对但阈值偏紧。比如某条频次规则定的是10分钟内3笔而用户当天在超市连刷了几次小额支付正好踩线。这类问题修的是阈值。把这三类分清楚很关键因为它们的修复方式和责任人完全不同。数据问题找数据团队逻辑问题找策略同学阈值问题策略同学自己就能改。5.3 修复方案降级、加白、还是拆条件确认是误杀之后有三种修复路径选哪种取决于影响面和紧急程度。最轻的是加白名单只对这一个用户放行。优点是见效快、影响面小、风险低缺点是治标不治本如果同类误杀有一千个用户你不可能加一千条白名单。加白只适合个案处理。中等的是调整规则动作从拒绝降级为二次验证或转人工。这样既不会直接放走风险也不会直接挡住用户把判断权交还给真实的人类行为。这个做法在大促期间特别常用因为大促时段的误杀代价远高于平时宁可放宽一点。最重的是拆条件或者调阈值改动规则本身。这是根治方案但风险最大因为改动会影响所有命中这条规则的流量可能把一部分本该拦住的也放走了。所以必须走灰度先影子跑观察新旧版本在同一批流量上的决策差异确认差异都在预期范围内再切量。注意任何一次规则修复都要留下记录说清楚为什么改、改成什么、影响了多少流量。我见过太多团队改完就忘三个月后同样的问题再来一次没人记得当初是怎么处理的。6. 灰度、监控与规则迭代的日常6.1 影子模式跑一周再放量新规则上线前必须先跑影子模式这一点没有商量余地。影子模式的意思是规则照常计算命中日志照常记录但决策动作不生效用户端完全无感。这样你可以拿到这条规则在真实流量上的完整表现包括命中量、命中的人群分布、命中的时间段分布、以及命中的账号后续有没有出现欺诈行为。为什么至少跑一周因为用户行为的周期性在一天和一周内都很明显。工作日和周末、白天和深夜、发薪日和月末支付行为差异巨大。只跑一天你看到的可能是一个特殊时段的表现据此放量很容易出偏差。一周能覆盖大部分周期是比较稳妥的观察窗口。影子模式期间重点看两件事。一是命中量级如果一天命中几十万笔那这条规则放量之后会直接把审核队列和客服电话打爆得先想办法收窄。二是命中人群的质量抽样看看命中的人里有多少明显是正常用户如果浓度太低就说明规则区分度不够。6.2 监控面板上必须有的六个指标规则上线之后的日常运营靠的是一个说得清楚的监控面板。我把必须有的指标归成六个。命中量按规则、按小时统计的命中次数用来发现突增突降。覆盖率这条规则贡献的决策占总决策的比例用来衡量规则的重要性长期覆盖率接近零的规则就是该退休了。准确率命中规则的用户中后续被确认为风险的比例。这个指标获取有滞后通常用一周或一个月的窗口来算。误杀率命中规则的用户中后续出现自证正常行为的比例跟准确率互补。拦截资损这条规则实际挡下来的金额用来衡量规则的经济价值。一条准确率高但拦的都是几块钱订单的规则优先级其实不高。响应耗时这条规则给整个决策链路增加的延迟。实时链路对耗时极其敏感一条算得慢的规则足以拖垮整条链路。这六个指标放在一个面板上配合每个规则的负责人就形成了一个基本的规则治理闭环。每周过一遍该收紧的收紧该放松的放松该下线的下线。6.3 规则的退休机制大部分团队只管加规则不管删规则结果规则库越滚越大最后没人敢动。这是我见过最普遍的规则治理问题。规则和人一样需要退休。我给规则设了三个退休触发条件。第一连续三个月零命中或者命中量低到统计上无意义直接下线。第二连续三个月准确率低于一个底线说明这条规则抓的东西已经不构成风险或者上游数据变了导致它失效先下线再研究。第三被新的规则完全覆盖旧规则的存在只是增加耗时直接下线。下线也要走流程不能直接删。先进观察模式只记录不生效跑两周看看有没有什么异常确认无影响再彻底移除。移除之后的规则配置保留在版本库里万一后面又要用直接翻历史版本恢复就行。这套机制跑起来之后规则库能保持一个相对精简的状态每条规则都有明确的使命和明确的责任人出问题也能快速定位。我在实际操作中的体会是风控规则这事儿最难的不是写规则是克制加规则的冲动。每次有新的欺诈案件出现第一反应总是再加一条规则吧但实际上很多时候应该做的是看看已有规则哪里漏了是阈值松了还是字段缺了。规则数量本身没有意义能用最少的规则覆盖最多的风险同时把误杀压在可接受范围内这才是这门手艺的目标。我见过把规则数从四百条精简到一百二十条、拦截效果反而变好的案例靠的就是一条条复盘、合并、淘汰。这个过程枯燥且没有成就感但它比写新规则有价值得多。