4. 缓存技术选型与设计:本地缓存与分布式缓存的对比及淘汰策略
在风控系统中,缓存是降低数据库压力、提升规则计算速度的核心手段。本章聚焦于缓存技术的选型与设计,从本地缓存(Caffeine/Guava)与分布式缓存(Redis/Memcached)的对比出发,深入分析淘汰策略(LRU/LFU/TTL)及数据结构设计,为后续的异步任务编排提供高性能、高可用的缓存底座。
4.1 本地缓存 vs 分布式缓存:核心对比
风控场景中,本地缓存适用于单节点高频访问的静态数据(如规则元数据、黑白名单),而分布式缓存适用于跨节点共享的实时数据(如用户画像、设备指纹)。以下从多个维度进行对比:
| 对比维度 | 本地缓存 (Caffeine/Guava) | 分布式缓存 (Redis/Memcached) |
|---|---|---|
| 数据一致性 | 单机强一致,无需网络同步 | 最终一致,依赖网络协议 |
| 访问延迟 | 纳秒级(堆内访问) | 微秒级(网络 I/O) |
| 容量上限 | 受限于 JVM 堆内存 | 可水平扩展至 TB 级 |
| 持久化 | 不支持(重启即丢失) | 支持 RDB/AOF 持久化 |
| 典型场景 | 规则引擎元数据、频率计数器 | 用户会话、实时黑名单、设备指纹 |
| 淘汰策略 | LRU/LFU/TTL 组合 | LRU/LFU/TTL/随机淘汰 |
选型建议: 风控系统中,建议采用“本地缓存 + 分布式缓存”两级架构。本地缓存用于高频读取的静态数据(如规则配置),分布式缓存用于跨节点共享的实时数据(如用户风险等级)。两级缓存可显著降低分布式缓存的 QPS 压力。
4.2 缓存淘汰策略:LRU、LFU 与 TTL
淘汰策略决定了缓存空间不足时如何驱逐旧数据。风控场景中,不同数据对时效性和访问频率的要求不同,需选择合适的策略。
- LRU(Least Recently Used):淘汰最近最少使用的数据。适用于访问模式有明显时间局部性的场景,如用户会话缓存。
- LFU(Least Frequently Used):淘汰访问频率最低的数据。适用于热点数据相对稳定的场景,如规则命中统计。
- TTL(Time To Live):基于过期时间淘汰。适用于对时效性要求严格的数据,如验证码、临时令牌。
实践要点: Caffeine 默认采用 W-TinyLFU 算法,结合 LRU 与 LFU 的优点,能有效应对突发流量和冷热数据切换。Redis 支持 volatile-lru、allkeys-lru、volatile-lfu、allkeys-lfu 等多种策略,建议风控系统使用 volatile-lru 或 volatile-lfu,仅对设置了 TTL 的 key 进行淘汰。
4.3 缓存数据结构设计
风控系统中,缓存的数据结构直接影响查询效率和内存占用。以下列出常见的数据结构及其适用场景:
| 数据结构 | 存储内容 | 典型风控场景 | 优势 |
|---|---|---|---|
| String | 单个值(JSON/序列化对象) | 用户风险等级、设备指纹 | 简单、高效,支持原子操作 |
| Hash | 字段-值映射 | 用户画像(多属性聚合) | 节省内存,支持部分更新 |
| List | 有序列表 | 事件队列、操作日志 | 支持双向操作,适合 FIFO |
| Set | 无序集合(去重) | 黑名单、白名单 | 支持交集/并集运算 |
| Sorted Set | 有序集合(带分数) | 排行榜、频率计数器 | 支持范围查询和排序 |
设计陷阱: 避免将大对象(如完整用户画像)直接存入 String 类型,会导致内存膨胀和序列化开销。建议使用 Hash 结构按字段存储,或拆分为多个小 key。对于黑名单等集合数据,优先使用 Set 而非 List,以利用去重和集合运算能力。
4.4 两级缓存架构设计
风控系统推荐采用“本地缓存(L1)+ 分布式缓存(L2)”的两级架构,兼顾性能与一致性。本地缓存使用 Caffeine,分布式缓存使用 Redis。
- L1 本地缓存(Caffeine):存储规则元数据、静态配置、频率计数器。容量控制在 100MB 以内,淘汰策略使用 W-TinyLFU + TTL。
- L2 分布式缓存(Redis):存储用户会话、实时黑名单、设备指纹、风险等级。容量按需扩展,淘汰策略使用 volatile-lru。
- 一致性策略:本地缓存设置较短的 TTL(如 5 秒),分布式缓存更新时通过消息队列广播失效通知。
4.5 缓存设计最佳实践
- Key 设计规范:使用业务前缀 + 冒号分隔,如
risk:user:12345:level,便于管理和监控。 - 内存预估:根据数据量和 TTL 估算内存占用,预留 20% 的 buffer 空间。
- 缓存穿透防护:对不存在的数据也缓存空值(设置短 TTL),或使用布隆过滤器。
- 缓存雪崩防护:设置随机 TTL 偏移量,避免大量 key 同时过期。
- 监控告警:监控缓存命中率、内存使用率、淘汰次数,低于阈值时触发告警。
代码示例:Caffeine 本地缓存配置
Cache<String, UserRiskLevel> riskCache = Caffeine.newBuilder()
.maximumSize(10_000) // 最大条目数
.expireAfterWrite(5, TimeUnit.SECONDS) // 写入后 5 秒过期
.recordStats() // 开启统计
.build(key -> loadFromRedis(key)); // 缓存未命中时回源