风控实时特征总拖慢 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 为什么事件必须有 eventId8.2 乱序怎么处理8.3 失败重试怎么做九、主链路故障时怎么兜底这是线上最容易被问到的点9.1 fail-open9.2 fail-close9.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_1h2.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_typeRedis 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 列表特征服务批量拉取映射成统一上下文规则引擎只做内存中的表达式计算伪代码示例ListFeatureRequestrequestsfeaturePlanner.plan(scene,request);MapString,ObjectfeatureMapfeatureClient.batchGet(requests);RiskContextcontextRiskContext.builder().request(request).featureMap(featureMap).build();RiskDecisiondecisionruleEngine.evaluate(context);7.1 特征服务接口建议长这样classFeatureBatchQueryRequest{Stringscene;StringrequestId;ListFeatureItemitems;}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。最后说上线影子计算、灰度放量、保留回滚。你这样回答面试官一般能听出来你不是只会背概念。十五、结语实时特征系统真正决定价值的不是“指标写得多不多”而是规则要的时候能不能及时拿到高峰期会不会拖慢主链路数据出错时能不能快速回滚和追查如果只记一个原则我更建议记这句先把读写链路拆开再把窗口语义定准最后才谈更复杂的实时计算技巧。下篇预告如果你愿意我下一篇可以继续按这个粒度往下写风控离线画像与特征仓怎么分层、怎么回灌、怎么服务化风控命中日志与决策日志表结构、快照策略、审计追踪怎么做风控规则回放平台样本池、回放任务、结果对比怎么设计评论区告诉我你最关心哪块我继续按真实项目的粒度拆。