特征计算加速:实时特征存储选型与优化

特征计算,说白了就是风控系统的「心脏」。你规则写得再漂亮,模型训练得再准,特征算不出来或者算得太慢,一切都白搭。我在做实时风控系统的时候,踩过最多的坑就在这一块。今天咱们就聊聊特征存储选型、预计算策略、滑动窗口优化和特征降维这四件事。

一、实时特征存储选型:Redis vs Aerospike

先说说存储。特征数据要快,要能扛住高并发,还得支持一些简单的聚合操作。市面上最常用的两个方案就是 Redis 和 Aerospike。我两个都用过,各有各的脾气。

Redis:简单粗暴,但内存是硬伤

Redis 的好处不用我多说——快,真的快。单机 QPS 能到 10 万+,而且数据结构丰富。做实时特征存储,我一般用 Hash 结构来存用户的多维特征,用 Sorted Set 来做滑动窗口。

# 存储用户特征(Hash)
HSET user:10001:features age 28 income 15000 credit_score 720

# 滑动窗口计数(Sorted Set)
ZADD user:10001:events:1h 1680000000 "login"
ZADD user:10001:events:1h 1680000100 "payment"
ZREMRANGEBYSCORE user:10001:events:1h 0 1679996400
ZCARD user:10001:events:1h

但 Redis 有个致命问题——内存太贵了。你想想看,一个用户存几十个特征,几百万用户就是几十 GB。我见过一个项目,Redis 内存飙到 200GB,一个月云账单看得老板直皱眉。

注意:Redis 的持久化机制(RDB/AOF)在高频写入场景下会有性能抖动。我曾经遇到过 AOF 重写时 CPU 飙到 100%,导致特征查询超时。建议关闭持久化,或者用 Redis Cluster 做读写分离。

Aerospike:为实时特征而生

Aerospike 是我后来才接触的。说实话,第一次用的时候有点不习惯,但用顺手之后真香。它的设计理念就是「闪存优先」,数据可以存在 SSD 上,但访问速度依然很快。

对比维度 Redis Aerospike
存储介质 纯内存 内存+SSD
单节点 QPS 10万+ 100万+
数据持久化 RDB/AOF(有性能损耗) 原生支持,无额外开销
成本 高(全内存) 低(SSD为主)
数据结构 丰富(Hash, List, Sorted Set等) 简单(KV + Map)

我个人的习惯是:如果特征数据量在 100GB 以内,用 Redis 就够了,开发成本低。如果超过 500GB,或者对成本敏感,果断上 Aerospike。中间那一段,看团队技术栈。

小技巧:Aerospike 的 UDF(用户自定义函数)可以在服务端做特征聚合,减少网络开销。比如在服务端直接计算过去1小时的交易次数,不用把原始数据拉到客户端再算。

二、特征预计算策略

实时计算最大的敌人是什么?是延迟。每次请求都从头算一遍特征,那肯定慢。预计算就是「把功夫下在平时」。

我常用的预计算策略有三种:

  1. 离线预计算 + 在线缓存:每天凌晨用 Spark 批量算好基础特征,写入特征存储。在线请求时直接读取,几乎零延迟。
  2. 增量预计算:用户每产生一个事件,就更新一次特征值。比如用户完成一笔交易,立即更新「近1小时交易次数」这个特征。
  3. 分层预计算:把特征分成高频、中频、低频三层。高频特征(如登录次数)实时更新,低频特征(如用户画像)每天更新一次。
// 增量预计算示例(伪代码)
public void onEvent(UserEvent event) {
    String key = "user:" + event.getUserId() + ":features";
    
    // 原子更新:近1小时交易次数 +1
    featureStore.hincrBy(key, "txn_count_1h", 1);
    
    // 更新最近交易时间
    featureStore.hset(key, "last_txn_time", System.currentTimeMillis());
    
    // 更新滑动窗口
    featureStore.zadd("user:" + event.getUserId() + ":txn_timestamps", 
                      System.currentTimeMillis(), UUID.randomUUID().toString());
}

嗯,这里要注意:增量预计算虽然快,但容易出错。比如事件重复消费、时序错乱,都会导致特征值不准。我建议加上「幂等性校验」,或者用事件时间(Event Time)而不是处理时间(Processing Time)。

三、滑动窗口优化

滑动窗口是实时风控里最常用的特征类型。比如「过去5分钟登录次数」「过去1小时交易金额」。但滑动窗口也是最容易出性能问题的地方。

为什么会这样?因为每个请求都要查窗口内的数据,如果窗口很大(比如7天),数据量就很大,查询自然慢。

我分享几个优化思路:

  • 用计数器代替全量存储:如果只需要计数,不要存所有事件的时间戳。用多个小窗口(比如每分钟一个计数器)来近似滑动窗口。
  • 预聚合 + 时间桶:把时间切成桶(比如1分钟一个桶),每个桶里存聚合值。查询时只需要读最近N个桶的数据。
  • 本地缓存 + 异步刷新:在应用层缓存最近几秒的窗口结果,用异步线程去更新。这样大部分请求直接走缓存,只有缓存过期才去查存储。
// 时间桶滑动窗口实现
public class TimeBucketWindow {
    private static final int BUCKET_SIZE_MS = 60_000; // 1分钟一个桶
    private static final int WINDOW_BUCKETS = 60;     // 1小时 = 60个桶
    
    private final AtomicLongArray buckets = new AtomicLongArray(WINDOW_BUCKETS);
    
    public void add(long timestamp) {
        int index = (int)((timestamp / BUCKET_SIZE_MS) % WINDOW_BUCKETS);
        buckets.incrementAndGet(index);
    }
    
    public long getCount(long currentTime) {
        long sum = 0;
        long currentBucket = currentTime / BUCKET_SIZE_MS;
        for (int i = 0; i < WINDOW_BUCKETS; i++) {
            long bucketTime = currentBucket - i;
            if (bucketTime < 0) break;
            int index = (int)(bucketTime % WINDOW_BUCKETS);
            sum += buckets.get(index);
        }
        return sum;
    }
}
核心思路:用空间换时间。时间桶方案虽然会多占一点内存,但查询时间复杂度从 O(N) 降到了 O(1),对于高并发场景来说,这笔买卖太划算了。

四、特征降维

最后聊聊特征降维。很多团队做实时风控,恨不得把几百个特征全用上。结果呢?计算慢、存储贵、模型还容易过拟合。

我见过最夸张的一个项目,一个用户存了 200 多个特征,每次请求要算 30 秒。后来我们做了降维,只保留 20 个核心特征,效果反而更好了。

常用的降维方法:

  • 业务理解驱动:跟业务方聊,哪些特征是真的有用的?哪些是「我觉得可能有用」的?砍掉后者。
  • 特征重要性排序:用随机森林或 XGBoost 跑一遍,看每个特征的重要性分数。低于阈值的直接去掉。
  • 特征交叉与组合:把多个弱特征组合成一个强特征。比如「近1小时登录次数」和「近1小时支付次数」可以组合成「近1小时活跃度」。
  • Embedding 化:对于高基数类别特征(如设备ID、IP地址),用 Embedding 降维到 16 维或 32 维向量。

我曾经在一个支付风控项目里,把特征从 150 个降到了 25 个。存储成本降了 80%,计算延迟从 200ms 降到了 20ms,AUC 反而从 0.85 升到了 0.88。你想想看,有时候「少即是多」。

避坑指南:我曾经犯过一个错误——为了追求降维效果,把所有特征都做了 PCA 变换。结果模型上线后,业务方问「这个特征是什么意思?」我答不上来。后来我学乖了:降维可以,但要保留可解释性。至少核心特征要能说清楚。

知识体系总览

下面这张图是我对本章内容的总结。你可以看到,特征计算加速不是单一的技术选型,而是一套组合拳。

特征计算加速知识体系 特征计算加速 存储选型 Redis Aerospike 内存 vs SSD 成本与性能权衡 预计算策略 离线预计算 增量预计算 分层预计算 幂等性校验 滑动窗口优化 计数器替代全量 时间桶预聚合 本地缓存 O(N) → O(1) 特征降维 业务理解驱动 特征重要性排序 特征交叉组合 Embedding 化

说白了,特征计算加速没有银弹。你要根据业务场景、数据量、成本预算来综合选型。我个人的经验是:先做降维,再选存储,最后优化计算逻辑。顺序搞反了,容易白费功夫。

好了,这一章的内容就到这里。记住:特征计算是风控系统的「心脏」,心脏跳得快不快,就看你这四件事做得怎么样。


公众号:蓝海资料掘金营,微信deep3321