风控实时特征总拖慢 RT?滑动窗口、实时计数、聚合更新到底该怎么做(可落地版)
风控实时特征总拖慢 RT?滑动窗口、实时计数、聚合更新到底该怎么做(可落地版)
这篇不讲“实时特征很重要”这种空话,直接按真实项目来拆:入口请求长什么样、特征怎么算、Redis 怎么存、规则怎么取、更新怎么异步、故障怎么降级。
目标是你看完后,能把“实时特征计算”从面试回答变成一套真能上线的方案。
🦅个人主页
🐼GitHub主页
文章目录
- 风控实时特征总拖慢 RT?滑动窗口、实时计数、聚合更新到底该怎么做(可落地版)
- 一、先看一个真实场景:为什么实时特征经常是风控 RT 的第一瓶颈
- 二、先分层:哪些特征必须实时,哪些不要硬塞进主链路
- 2.1 哪些必须进实时链路
- 2.2 哪些不要硬做实时
- 三、按真实工程拆链路:读链路和写链路一定要分开看
- 3.1 决策读链路
- 3.2 行为写链路
- 3.3 为什么我不建议“请求里同步加计数再查”
- 四、特征定义表要先设计好,不然越做越乱
- 五、Redis Key 怎么设计:别光顾着能用,要考虑排障和扩展
- 5.1 不推荐的 Key 设计
- 5.2 TTL 怎么定
- 六、三类最常见实时特征,具体怎么做
- 6.1 计数类特征:分桶滑动窗口是最常见方案
- 6.2 去重类特征:精确和近似要分场景
- 6.3 最近一次行为类:Hash 或单值覆盖
- 七、一次真实请求里,规则引擎到底怎么拿特征
- 7.1 特征服务接口建议长这样
- 八、异步更新链路怎么做:真正难点在幂等和乱序
- 8.1 为什么事件必须有 eventId
- 8.2 乱序怎么处理
- 8.3 失败重试怎么做
- 九、主链路故障时怎么兜底:这是线上最容易被问到的点
- 9.1 fail-open
- 9.2 fail-close
- 9.3 介于两者之间的做法
- 十、性能优化要从 4 个维度看,不只是“加缓存”
- 10.1 查询批量化
- 10.2 热点隔离
- 10.3 规则侧复用
- 10.4 更新削峰
- 十一、监控到底监什么:没有这些指标,出事很难救
- 11.1 决策链路指标
- 11.2 存储层指标
- 11.3 写链路指标
- 11.4 特征质量指标
- 十二、上线步骤怎么走:别一上来就全量拦截
- 12.1 影子计算
- 12.2 联调对比
- 12.3 小流量灰度
- 12.4 先接评分,再接强拦截
- 12.5 保留一键回滚能力
- 十三、常见坑位,我按真实线上问题给你总结一下
- 13.1 把“自然分钟窗口”当“滑动窗口”
- 13.2 没有定义默认值
- 13.3 把所有特征都同步写
- 13.4 去重策略没选对
- 13.5 没有做特征回溯能力
- 十四、面试里怎么讲,才像真做过
- 十五、结语
- 下篇预告
一、先看一个真实场景:为什么实时特征经常是风控 RT 的第一瓶颈
假设我们现在做的是登录风控,主链路要求:
- 网关到返回结果总 RT 最好控制在
50ms以内 - 风控决策本身最好控制在
10ms~20ms - 单机 QPS 高峰在
3000+ - 决策依赖以下特征:
user_login_fail_cnt_10mdevice_login_user_cnt_30mip_login_req_cnt_1mdevice_last_login_city
这类特征有两个共同点:
- 请求一来就要查
- 请求处理完还要立刻更新
也就是说,实时特征系统同时处在:
- 读链路:规则引擎判断时必须快速拿到值
- 写链路:行为事件发生后必须尽快把值更新进去
所以它天然最容易出下面这些问题:
- 规则没慢,Redis 查慢了
- 单条规则不慢,多条规则一起查就慢
- 分钟窗口写得不准,结果规则看起来“逻辑对”,但效果不对
- 高峰期写放大,反过来把读链路拖慢
一句话总结:
实时特征系统难的从来不是“存一个计数”,而是“在高并发下把读写两条链路同时稳住”。
二、先分层:哪些特征必须实时,哪些不要硬塞进主链路
很多系统做慢,第一步就错了:把所有看起来有用的指标都塞进实时特征。
更稳的做法是先分类。
| 类型 | 时效要求 | 典型例子 | 推荐实现 |
|---|---|---|---|
| 实时特征 | 秒级 | 近 1 分钟请求次数、近 10 分钟失败次数 | Redis / 流状态 |
| 准实时特征 | 分钟级 | 近 6 小时不同设备数、近 1 天订单数 | 流聚合 + 分钟级回写 |
| 离线特征 | 小时/天级 | 历史退款率、历史风险等级、画像分层 | 数仓加工 + 服务化输出 |
2.1 哪些必须进实时链路
我一般会用两个标准判断:
- 这个特征是否会直接影响当前请求的“放行 / 挑战 / 拒绝”
- 如果延迟 1~5 分钟更新,会不会明显降低规则价值
例如下面这些通常必须实时:
login_fail_cnt_10mpay_req_cnt_device_1mwithdraw_req_cnt_card_30mcoupon_receive_user_cnt_1h
2.2 哪些不要硬做实时
这些更适合放到准实时或离线:
- 近 30 天退款率
- 近 7 天交易金额分位
- 用户历史风险等级
- 设备稳定性分
原因很简单:
- 计算成本高
- 时效性没那么强
- 放进主链路只会拖 RT
经验原则:
实时链路只保留“本次决策必须马上知道”的特征,其余一律降级。
三、按真实工程拆链路:读链路和写链路一定要分开看
3.1 决策读链路
读链路的目标只有一个:尽快把本次决策需要的特征取回来。
一个典型流程是:
- 业务请求进入风控网关
- 规则引擎先算出本次请求需要哪些特征
- 特征服务批量查 Redis
- 返回规则上下文
- 规则引擎执行表达式
- 输出最终处置
注意这里最好只做“读”,不要在规则执行过程中同步更新计数。
3.2 行为写链路
写链路的目标是:把本次请求对应的行为事件异步更新到实时特征存储。
一个典型流程是:
- 登录/下单/支付请求处理完成
- 业务服务投递一条标准事件到 MQ
- 实时特征计算服务消费事件
- 按特征定义更新 Redis / 状态存储
- 更新监控和质量统计
这条链路和读链路最大的区别是:
- 它可以异步
- 它允许短暂延迟
- 它要优先解决写放大和幂等问题
3.3 为什么我不建议“请求里同步加计数再查”
很多早期系统会这么写:
- 请求进来
- 先
INCR - 再
GET - 再做规则判断
短期能跑,但问题很多:
- 同一请求读写耦合,RT 更抖
- 更新失败会直接影响决策
- 重试请求容易把计数写乱
更稳的做法通常是:
- 决策读链路只读
- 更新走异步事件
- 对“必须先写后读”的极少数特征单独处理
四、特征定义表要先设计好,不然越做越乱
线上系统真正容易失控的,不是 Redis 命令,而是“大家对同一个特征理解不一致”。
所以我很建议先做一张特征定义表,例如:
CREATETABLErisk_feature_meta(idBIGINTPRIMARYKEY,feature_codeVARCHAR(64)NOTNULL,feature_nameVARCHAR(128)NOTNULL,scene_codeVARCHAR(32)NOTNULL,entity_typeVARCHAR(32)NOTNULL,calc_typeVARCHAR(32)NOTNULL,window_size_secINT,store_typeVARCHAR(32)NOTNULL,ttl_secINT,default_valueVARCHAR(64),online_requiredTINYINTNOTNULL,ownerVARCHAR(64),statusTINYINTNOTNULL,created_atDATETIME,updated_atDATETIME);建议至少固定下面这些字段:
feature_code:特征编码,例如login_fail_cnt_10mscene_code:场景,例如loginentity_type:主体,例如user/device/ipcalc_type:计数 / 求和 / 去重 / 最近一次window_size_sec:窗口大小store_type:Redis Counter / ZSet / HLL / Hashttl_sec:过期时间online_required:是否主链路强依赖
这张表的意义不只是配置。
它直接决定:
- 规则平台怎么引用特征
- 实时计算服务怎么更新
- 故障时默认值怎么兜底
- 运维怎么排查
五、Redis Key 怎么设计:别光顾着能用,要考虑排障和扩展
我比较推荐这样的命名规范:
risk:feat:{scene}:{feature}:{entityType}:{entityId}:{bucket}例如:
risk:feat:login:fail_cnt:user:123:202604261230risk:feat:login:req_cnt:ip:1.2.3.4:202604261230risk:feat:pay:sum_amt:device:abcd:2026042612
为什么要把层次拆这么细:
scene方便按场景隔离feature方便快速定位是哪类特征entityType避免 user 和 device 混用bucket方便做时间窗口聚合
5.1 不推荐的 Key 设计
比如下面这种:
risk_login_123问题很大:
- 看不出特征含义
- 看不出窗口
- 看不出主体类型
- 后续扩展多个场景会越来越乱
5.2 TTL 怎么定
经验上不要只等于窗口本身。
例如:
10m窗口,TTL 可以配15m ~ 20m1h窗口,TTL 可以配70m ~ 90m
这样做是为了:
- 防止边界时间抖动
- 给重试和延迟消费留冗余
六、三类最常见实时特征,具体怎么做
6.1 计数类特征:分桶滑动窗口是最常见方案
比如特征:
login_fail_cnt_10m
推荐做法:
- 把 10 分钟拆成 10 个 1 分钟桶
- 每次失败事件只更新当前桶
- 查询时聚合最近 10 个桶
写入示例:
INCR risk:feat:login:fail_cnt:user:123:202604261230 EXPIRE risk:feat:login:fail_cnt:user:123:202604261230 1200读取逻辑大概是:
- 根据当前时间向前推 10 个桶
- 批量
MGET - 求和
优点:
- 实现简单
- 成本可控
- 读写都比较稳
缺点:
- 需要聚合多个桶
6.2 去重类特征:精确和近似要分场景
比如:
device_bind_user_cnt_1dip_distinct_user_cnt_6h
如果是资金场景、强风控场景,我更倾向:
Set
如果是流量预警、趋势统计,我更倾向:
HyperLogLog
选择逻辑很简单:
- 要精确,就多花点资源
- 能容忍误差,就用近似结构省成本
6.3 最近一次行为类:Hash 或单值覆盖
例如:
last_login_citylast_login_devicelast_pay_channel
这种特征本质不是窗口计数,而是“最近一次状态快照”。
一般可以直接:
SET- 或者写进一个
Hash
例如:
HSET risk:feat:last_login:user:123 city "shanghai" ts 1714105800 EXPIRE risk:feat:last_login:user:123 259200七、一次真实请求里,规则引擎到底怎么拿特征
最怕的写法是:
- 每条规则自己去 Redis 查一次
如果一个请求需要 20 个特征、10 条规则,那这个链路很容易放大成几十次网络往返。
更稳的做法是:
- 规则平台先把本场景依赖的特征提前编译出来
- 本次请求一次性生成 Key 列表
- 特征服务批量拉取
- 映射成统一上下文
- 规则引擎只做内存中的表达式计算
伪代码示例:
List<FeatureRequest>requests=featurePlanner.plan(scene,request);Map<String,Object>featureMap=featureClient.batchGet(requests);RiskContextcontext=RiskContext.builder().request(request).featureMap(featureMap).build();RiskDecisiondecision=ruleEngine.evaluate(context);7.1 特征服务接口建议长这样
classFeatureBatchQueryRequest{Stringscene;StringrequestId;List<FeatureItem>items;}classFeatureItem{StringfeatureCode;StringentityType;StringentityId;}这样做的好处是:
- 规则引擎不用自己拼 Redis Key
- 特征层可以统一做容错、缓存、监控
- 后续切换底层存储不会影响规则层
八、异步更新链路怎么做:真正难点在幂等和乱序
假设我们要更新登录失败次数。
最简单的事件长这样:
{"eventId":"evt_20260426_001","scene":"login","eventType":"login_fail","userId":"123","deviceId":"abcd","ip":"1.2.3.4","eventTime":1714105800123}8.1 为什么事件必须有 eventId
因为 MQ 消费天生可能重复。
如果没有eventId,你很难保证:
- 消费重试不会多加一次计数
- 多个消费者不会重复写
所以我很建议:
- 先做事件去重
- 再做特征更新
8.2 乱序怎么处理
比如:
- 10:00:01 的事件晚到了
- 10:00:03 的事件先消费了
对纯计数场景通常问题不大,因为还是落在自己的桶里。
但对“最近一次行为类”就要特别小心。
我一般会加一个判断:
- 只有当
eventTime晚于当前存储时间时,才覆盖最近一次值
8.3 失败重试怎么做
建议最少有两层:
- 消费端自动重试
- 死信队列 + 人工补偿
否则实时特征一旦漏写,规则效果会悄悄变差,而且不容易第一时间发现。
九、主链路故障时怎么兜底:这是线上最容易被问到的点
如果 Redis 抖动、特征服务超时,不能简单说“那就报错”。
不同场景要配置不同兜底策略:
9.1 fail-open
特征取不到时默认放行。
适合:
- 登录
- 普通浏览
- 低风险领券
优点:
- 不容易伤害正常用户
风险:
- 可能漏掉部分风险请求
9.2 fail-close
特征取不到时默认拒绝或挑战。
适合:
- 高风险提现
- 大额支付
- 资金转出
优点:
- 更保守
风险:
- 用户体验差,误伤成本高
9.3 介于两者之间的做法
我在项目里更常用的是:
- Redis 超时就进入“挑战”而不是直接拒绝
例如:
- 触发验证码
- 触发人脸核验
- 限额处理
这比一刀切更稳。
十、性能优化要从 4 个维度看,不只是“加缓存”
10.1 查询批量化
这是最直接的收益点。
能批量拿就不要单个拿,能在一个 pipeline 里完成就不要拆多次连接。
10.2 热点隔离
有些 Key 天生会热,比如:
- 大公网出口 IP
- 热门活动的公共设备
- 攻击期间的高频账号
这些最好单独做:
- Key 分片
- 限流保护
- 热点监控
10.3 规则侧复用
如果三条规则都依赖login_fail_cnt_10m,不要查三次。
一个请求内同一个特征只算一次、只读一次。
10.4 更新削峰
如果活动高峰一来,写链路可能比读链路更容易打爆 Redis。
常见做法:
- MQ 缓冲
- 批量合并消费
- 对低价值统计延迟写入
十一、监控到底监什么:没有这些指标,出事很难救
建议至少看这几类指标:
11.1 决策链路指标
- 决策 RT P50/P95/P99
- 特征查询 RT
- 特征查询失败率
- 决策降级率
11.2 存储层指标
- Redis QPS
- 热点 Key 数量
- 超时率
- 连接池等待时间
- 大 Key 数量
11.3 写链路指标
- MQ 堆积长度
- 消费延迟
- 事件去重命中率
- 事件写失败率
11.4 特征质量指标
- 特征缺失率
- 特征延迟分布
- 异常值比例
- 特征和规则命中率的联动变化
如果只是监控“Redis 活着没”,那远远不够。
真正有用的是:
- 知道是读慢了
- 还是写堵了
- 还是某类特征数据本身就不完整了
十二、上线步骤怎么走:别一上来就全量拦截
我比较推荐下面这个顺序:
12.1 影子计算
新特征先接行为事件、正常算值,但不参与真正决策。
目的:
- 看稳定性
- 看数值分布
- 看和预期口径是否一致
12.2 联调对比
拿历史样本和线上实时请求做抽样对比:
- 同一主体的窗口值是否一致
- 边界时间是否有明显偏差
12.3 小流量灰度
按用户分层、按渠道、按业务线:
1% -> 5% -> 20% -> 全量
12.4 先接评分,再接强拦截
不要一开始就直接拒绝。
更稳的做法是:
- 先做打分参考
- 再做挑战
- 最后再做拒绝
12.5 保留一键回滚能力
至少要能做到:
- 关闭某个场景的特征依赖
- 回退到旧阈值
- 切到降级模板
十三、常见坑位,我按真实线上问题给你总结一下
13.1 把“自然分钟窗口”当“滑动窗口”
规则写的是近 10 分钟,结果实现成每个自然分钟累加,然后粗暴取最近 10 个整分钟。
问题:
- 临界点误差大
- 容易被绕过
13.2 没有定义默认值
比如特征没取到,规则直接按null > 5或默认当 0。
问题:
- 某些规则直接失效
- 某些规则直接误杀
13.3 把所有特征都同步写
结果:
- 主链路 RT 抖动
- Redis 写放大
- 活动高峰更容易雪崩
13.4 去重策略没选对
明明是资金场景,却图省事用了 HLL。
结果:
- 精度不够
- 后面争议很大
13.5 没有做特征回溯能力
一旦怀疑某个特征口径不对,只能猜。
所以最好保留:
- 事件样本
- 特征值快照
- 版本信息
十四、面试里怎么讲,才像真做过
如果面试官问你:
风控实时特征怎么设计?
我更建议你按这个顺序答:
先说分层
实时、准实时、离线特征拆开,避免主链路过重。再说链路
决策链路只读,更新链路异步,批量查 Redis,规则层不直接拼 Key。再说数据结构
计数类用分桶滑动窗口,去重按精度选 Set/HLL,最近一次行为类单独建模。再说故障兜底
按场景配置 fail-open / fail-close / challenge。最后说上线
影子计算、灰度放量、保留回滚。
你这样回答,面试官一般能听出来你不是只会背概念。
十五、结语
实时特征系统真正决定价值的,不是“指标写得多不多”,而是:
- 规则要的时候能不能及时拿到
- 高峰期会不会拖慢主链路
- 数据出错时能不能快速回滚和追查
如果只记一个原则,我更建议记这句:
先把读写链路拆开,再把窗口语义定准,最后才谈更复杂的实时计算技巧。
下篇预告
如果你愿意,我下一篇可以继续按这个粒度往下写:
- 风控离线画像与特征仓:怎么分层、怎么回灌、怎么服务化
- 风控命中日志与决策日志:表结构、快照策略、审计追踪怎么做
- 风控规则回放平台:样本池、回放任务、结果对比怎么设计
评论区告诉我你最关心哪块,我继续按真实项目的粒度拆。
