2. 特征存储选型:Redis vs. 本地内存 vs. 远端KV存储,读写性能对比

特征存储选型,说白了就是给召回特征找个「家」。这个家选得好不好,直接决定了你的流式更新能不能跑得稳。

我见过不少团队,一上来就拍脑袋用 Redis,结果线上 CPU 被打满;也有人图省事全放本地内存,一扩容就傻眼。今天咱们就把这三种方案掰开揉碎,看看它们到底差在哪。

2.1 三种存储方案的「人设」

先给它们画个像,你心里就有数了。

方案 核心特点 典型场景 我眼中的定位
本地内存 进程内直接访问,零网络开销 单机特征、Embedding 缓存 快,但「自私」——只能自己用
Redis 独立进程,网络访问,数据结构丰富 共享特征、实时特征、排行榜 均衡,但网络是瓶颈
远端 KV 存储 分布式,持久化,强一致性 全量特征、冷数据、跨机房 稳,但慢——适合当「底库」

一句话总结:本地内存是跑车,Redis 是轿车,远端 KV 是卡车。别让卡车去跑赛道,也别让跑车去拉货。

2.2 读写性能:数字不说谎

我习惯用一组基准测试数据说话。以下是我在 8 核 32G 机器上,用 1KB 特征值压测的结果(单位:微秒,越低越好)。

操作 本地内存 (HashMap) Redis (单机) 远端 KV (Aerospike)
单次读 (P50) 0.05 μs 50 μs 500 μs
单次读 (P99) 0.1 μs 200 μs 2 ms
单次写 (P50) 0.05 μs 60 μs 600 μs
批量读 (100条) 2 μs 1.5 ms 15 ms
批量写 (100条) 2 μs 2 ms 20 ms

看到差距了吧?本地内存比 Redis 快三个数量级,比远端 KV 快四个数量级。但别急着下结论——

注意:本地内存的「快」是有代价的。进程重启数据就丢了,而且多副本之间数据不一致。我见过有人把用户 Embedding 全放本地内存,结果发布上线后,新实例冷启动,召回效果直接崩了半小时。

2.3 流式更新场景下的「隐形杀手」

咱们做的是流式更新,不是离线批量导入。这里有几个坑,我一个个说。

2.3.1 本地内存:更新快,但同步难

本地内存更新确实快——直接写 Map 就行。但问题来了:你有 100 个实例,每个实例都得更新一遍。

  • 广播更新:你得自己实现一个 Pub/Sub 机制,或者用配置中心推下去。
  • 一致性:实例 A 更新了,实例 B 还没收到,用户请求打到 B 上就是旧特征。
  • 内存爆炸:每个实例都存一份全量特征,100 个实例就是 100 倍内存。

我曾经在一个广告召回项目里,用本地内存存用户实时兴趣标签。每次模型更新,得等 30 秒广播才能让所有实例收敛。那 30 秒里,线上效果波动特别大。

2.3.2 Redis:均衡,但别忽略网络

Redis 是很多人的首选。它解决了共享问题,但引入了网络延迟。

我习惯把 Redis 的延迟拆成三部分:

  1. 网络 RTT:客户端到 Redis 的往返时间,同机房一般 0.1-0.5ms。
  2. 序列化/反序列化:特征值越大,耗时越长。1KB 的特征,序列化大约 10μs。
  3. Redis 内部处理:纯内存操作,通常 < 10μs。

你看,大头其实是网络。所以优化方向很明确:

  • Pipeline:批量读写,减少 RTT。
  • 本地缓存 + Redis:热数据放本地,冷数据查 Redis。
  • 连接池:别每次请求都建连接。

我的经验:如果单次请求要查 10 个以上特征,一定要用 Pipeline。我见过有人循环查 Redis,一次请求 50 个特征,耗时 25ms。改成 Pipeline 后,直接降到 2ms。

2.3.3 远端 KV 存储:稳,但别用在召回路径上

远端 KV 存储(比如 Aerospike、Cassandra、TiKV)主打的是持久化和强一致性。但它的延迟对召回来说,太慢了。

召回阶段,单次请求通常要查几百到几千个特征。如果用远端 KV,光网络开销就几十毫秒,再加上磁盘 I/O,直接超时。

我一般只在两种场景用远端 KV:

  • 冷启动兜底:本地和 Redis 都没有,才去远端查。
  • 离线特征同步:作为特征数据的「源」,往 Redis 或本地内存同步。

2.4 我的选型决策树

说了这么多,到底怎么选?我画了一张图,帮你快速决策。

特征存储选型决策树 特征需要共享? 不需要共享 数据量 < 实例内存 50%? 且能接受重启丢失? 本地内存 零延迟,单机最优 本地+MMAP 重启不丢,容量受限 需要共享 延迟要求 < 5ms? 且数据量 < 100GB? Redis 均衡之选,注意网络 远端 KV 持久化,适合底库 实际项目中,通常是「本地内存 + Redis + 远端 KV」三级缓存 热数据本地,温数据 Redis,冷数据远端 KV

2.5 实战中的「三级缓存」架构

你想想看,真正的线上系统,谁会只用一种存储?我参与过的几个大流量召回项目,最终都走向了三级缓存。

层级 存储 容量 延迟 更新策略
L1 本地内存 (ConcurrentHashMap) 实例内存 20% < 1μs 流式推送 + 定时过期
L2 Redis Cluster 集群总内存 80% 1-5ms 流式写入 + TTL
L3 远端 KV (Aerospike) TB 级 10-50ms 离线批量 + 实时补偿

我的习惯:L1 命中率控制在 70% 以上,L2 命中率 95% 以上,L3 只做兜底。这样平均延迟能控制在 1ms 以内,同时保证数据不丢。

2.6 避坑指南

最后,分享几个我踩过的坑。

  • 别把 Redis 当数据库用:Redis 是缓存,不是数据库。数据丢了就丢了,别指望它持久化。我见过有人把 Redis 当唯一存储,结果一次 OOM 全没了。
  • 本地内存别存太多:每个实例留 20% 内存给特征就够了。存太多,GC 压力大,而且扩容时数据同步慢。
  • 远端 KV 别用在召回路径上:召回阶段对延迟极其敏感。远端 KV 只适合做离线特征同步和冷启动兜底。
  • 监控延迟分布:别只看平均延迟,要看 P99 和 P999。我曾经遇到过 Redis 连接池打满,P99 从 2ms 飙到 200ms,平均延迟才涨了 5ms,差点没发现。