双 11 智能客服冷热数据分离架构:LRU 语义淘汰与多级二级缓存联动

发布时间:2026/10/24 2:06:29
双 11 智能客服冷热数据分离架构:LRU 语义淘汰与多级二级缓存联动

大促进入冲刺期,运维团队拿着 Redis 集群的内存容量图又找上了门。

“李工,虽然 Redis 语义缓存帮我们挡住了 80% 的大模型调用,但这几周压测下来,问答对的向量索引数据已经吃掉了 40GB 内存。按照这个速度,双 11 期间数以千万计的各种冷门问题一旦全部转成向量丢进 Redis,整个集群的物理内存分分钟就要被打穿。扩容物理机不仅审批流程冗长,更关键的是,大量的长尾冷数据在内存里长年睡大觉,纯属资源浪费啊!”

在传统互联网缓存架构中,“冷热分离”是老生常谈的话题;但在**“向量语义缓存”的语境下,冷热分离的难度直接翻了数倍。
因为普通的 Key-Value 缓存只需要比对 String 的哈希值,淘汰策略简单的 LRU 或 LFU 就能搞定;而语义缓存的核心是
高维向量相似度检索(HNSW / Flat Index)**。高维向量不仅占内存大,而且如果你把冷数据直接物理删除,当用户换一种刁钻句式再次提问时,系统就无法通过语义余弦相似度召回答案,进而导致缓存穿透,重新把压力甩回昂贵的大模型推理集群。

如何在保证“内存不爆、命中率不崩、延迟不涨”的前提下,给大模型语义缓存设计一套**“端本地 Caffeine + 集中式 Redis + 底层持久化向量库”**的多级冷热数据联动架构?

今天我们就把这套在双 11 战场上经过极限压力验证的解决方案完整拆解。


一、语义缓存冷热分级架构拓扑

在设计时,我们摒弃了“把所有向量一脑子全塞进 Redis 内存”的粗放模式,采用了严密的三级金字塔架构:

[用户实时提问] │ ▼ +─────────────────────────────────────────────────────────────+ | L1 本地极速层 (Caffeine In-Memory Cache) | | - 纯堆内内存,存储 Top 200 超高频大促规则(如发货、满减) | | - 精确匹配 + 极简轻量语义,耗时 < 1ms,挡住 40% 流量 | +──────────────────────────────┬──────────────────────────────+ │ 未命中 ▼ +─────────────────────────────────────────────────────────────+ | L2 集中式热点层 (Redis Vector Store + 热度衰减淘汰) | | - 存储近 3 天活跃的 10 万条热点向量数据 | | - HNSW 向量索引检索,相似度阈值 0.88,耗时 15ms | | - 基于自定义 LRU 与访问频次,冷数据自动沉降 | +──────────────────────────────┬──────────────────────────────+ │ 长期未访问冷数据 ▼ +─────────────────────────────────────────────────────────────+ | L3 离线冷海层 (Milvus / pgvector / ClickHouse 磁盘存储) | | - 存储全量数百万条历史问答对与店铺知识库 | | - 支持基于磁盘的 IVFFlat / DiskANN 索引,成本仅为内存的 1/10| +─────────────────────────────────────────────────────────────+

二、带访问热度跟踪的语义缓存元数据模型

传统的 Redis Vector Store 只存向量和 Payload,缺乏细粒度的“访问热度(Heat Score)”感知。我们在元数据中引入了基于牛顿冷却定律思想的动态热度评分机制。

每次向量被命中时,热度值向上跳跃;随着时间推移,热度值指数衰减。

package com.yali.ai.cache.tier; import java.io.Serializable; import java.time.LocalDateTime; public class SemanticCacheEntity implements Serializable { private String id; private String originalQuery; private String modelAnswer; private float[] vector; // 访问频次与时间戳 private long hitCount; private long lastAccessTime; // 动态热度得分 (0.0 ~ 100.0) private double heatScore; public SemanticCacheEntity(String id, String originalQuery, String modelAnswer, float[] vector) { this.id = id; this.originalQuery = originalQuery; this.modelAnswer = modelAnswer; this.vector = vector; this.hitCount = 1; this.lastAccessTime = System.currentTimeMillis(); this.heatScore = 50.0; // 初始基准热度 } /** * 当该条目被再次命中时触发热度更新 */ public synchronized void recordAccess() { long now = System.currentTimeMillis(); double elapsedHours = (now - lastAccessTime) / 3600000.0; // 基于时间衰减:每经过 1 小时热度衰减 10% this.heatScore = this.heatScore * Math.pow(0.9, elapsedHours); // 单次命中奖励热度 this.heatScore = Math.min(100.0, this.heatScore + 5.0); this.hitCount++; this.lastAccessTime = now; } public boolean isCold() { return this.heatScore < 10.0; } // Getter & Setter 略... public String getId() { return id; } public String getModelAnswer() { return modelAnswer; } public double getHeatScore() { return heatScore; } public long getLastAccessTime() { return lastAccessTime; } }

三、多级缓存联动与自动沉降服务

在 Spring Boot 服务层,我们通过组合设计模式将 Caffeine 本地缓存与 Redis 集中式向量缓存编排起来。

package com.yali.ai.cache.tier; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.Duration; import java.util.Optional; @Service public class MultiTierSemanticCacheManager { private static final Logger log = LoggerFactory.getLogger(MultiTierSemanticCacheManager.class); // L1: 本地 Caffeine 缓存极热问答对 private final Cache<String, String> localHotCache; // L2: Redis 向量与热度存储 private final RedisTemplate<String, Object> redisTemplate; private static final String REDIS_SEMANTIC_KEY_PREFIX = "ai:semantic:cache:"; public MultiTierSemanticCacheManager(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; this.localHotCache = Caffeine.newBuilder() .maximumSize(500) // 仅保留 500 个超高频问题 .expireAfterWrite(Duration.ofMinutes(30)) .recordStats() .build(); } /** * 多级查找问答缓存 */ public Optional<String> findAnswer(String userQuery, float[] queryVector) { // 1. 查询 L1 本地缓存(哈希精准匹配极热词条) String localAnswer = localHotCache.getIfPresent(userQuery); if (localAnswer != null) { log.debug("L1 Caffeine 命中极热缓存: query=[{}]", userQuery); return Optional.of(localAnswer); } // 2. 查询 L2 Redis 向量语义缓存(相似度余弦检索) // 此处封装了对 Redis Vector Index 的调用(返回最接近的 Entity) Optional<SemanticCacheEntity> redisHit = searchRedisVectorStore(queryVector); if (redisHit.isPresent()) { SemanticCacheEntity entity = redisHit.get(); entity.recordAccess(); // 异步回写更新热度 redisTemplate.opsForValue().set(REDIS_SEMANTIC_KEY_PREFIX + entity.getId(), entity, Duration.ofDays(3)); // 若热度极高,晋升到 L1 本地缓存 if (entity.getHeatScore() > 80.0) { localHotCache.put(userQuery, entity.getModelAnswer()); } return Optional.of(entity.getModelAnswer()); } // 3. L1/L2 均未命中,判定为缓存穿透,返回空走大模型推理 return Optional.empty(); } /** * 模拟 Redis 向量相似度检索 */ private Optional<SemanticCacheEntity> searchRedisVectorStore(float[] queryVector) { // 生产实操中调用 redisTemplate.execute 执行 FT.SEARCH 并计算余弦相似度 >= 0.88 // 此处返回模拟实体 return Optional.empty(); } /** * 每天凌晨执行冷数据归档淘汰定时任务 * 将热度低于阈值的长尾向量从 Redis 中清理,降级到持久化冷库(如 pgvector / 磁盘) */ @Scheduled(cron = "0 0 3 * * ?") public void evictColdVectors() { log.info("【大促运维】开始执行 Redis 语义缓存冷热数据淘汰清理任务..."); // 遍历扫描所有 ai:semantic:cache:* 检查 isCold() // 将 isCold == true 的实体转移到磁盘持久库,并从 Redis 内存中 DEL } }

四、双 11 极限压测效果与成本核算

在搭建了这套多级联动体系后,我们进行了长达 24 小时的全链路稳定性压测:

  1. L1 Caffeine 命中率:在模拟真实网购提问中,发货、满减、运费险等 200 个爆款规则在本地命中率达到了38.5%,这部分请求完全不需要出宿主机网卡,响应耗时几乎为0 毫秒;
  2. L2 Redis 内存释放:通过牛顿冷却定律的热度淘汰机制,Redis 集群只保留近 72 小时真正被高频访问的 8 万条向量,原本预估需要 60GB 的内存被死死压缩在8.5GB以内,内存占用暴降85%;
  3. P99 延迟表现:由于 L1 吸收了大量瞬时并发,Redis 向量索引的并发压力骤减,整个语义缓存链路的 P99 响应耗时由平时的 45ms 进一步压缩至12ms。

五、老鸭梨的避坑指南

落地多级语义缓存,有两项工程细节必须格外警惕:

  1. 大促大规则变更时的“一键广播击穿机制”:
    双 11 期间,运营可能会在晚上 10 点临时修改促销规则(比如临时追加“全场满 500 减 100”)。此时散落在几十台微服务容器本地的 L1 Caffeine 缓存必须支持秒级失效!必须通过 Redis Pub/Sub 广播或者 Nacos 配置下发,一键清理所有容器的本地缓存,坚决防止客服机器人向用户输出旧规则;
  2. 冷热沉降千万别在白天流量高峰搞:
    遍历 Redis 扫描热度、淘汰冷向量的定时任务,必须严格调度在凌晨三点到五点的业务低谷期。如果在晚上 8 点大促高峰期执行FT.SEARCH批量过滤,Redis 单线程会被长时间卡死,造成惨烈的全网瘫痪。