基于 Redis 的分布式语义缓存架构

基于 Redis 的分布式语义缓存架构 基于 Redis 的分布式语义缓存架构在多实例部署的高并发业务系统中大模型调用的成本和响应延迟始终是核心瓶颈。传统的 HTTP 缓存如基于 URL、参数 Hash 或精确文本匹配在面对大语言模型LLM场景时往往失效用户提问“如何配置 Nginx 反向代理”和“Nginx 反向代理怎么配置”两者的字符串 MD5 截然不同但语义完全等价。引入专门的向量数据库如 Milvus、Qdrant虽然功能完善但对于绝大多数中小型团队而言运维成本和架构复杂度过高。如果系统中本就依赖 Redis 作为基础缓存组件直接利用 Redis 配合轻量向量检索与余弦相似度计算即可搭建一套高性能、低延迟的分布式语义缓存。架构设计与缓存命中流程整套分布式语义缓存的流转逻辑包含三个关键环节轻量 Embedding 提取接收到用户 Query 后调用快速的小参数量 Embedding 模型如text-embedding-3-small或本地部署的bge-small-zh生成归一化的高维向量。两级匹配策略L1 精确 Hash 匹配针对完全相同的请求文本直接通过 Redis String / GET 读取耗时 1ms。L2 分布式语义匹配若 L1 未命中则从 Redis Hash / Vector 集合中检索候选向量计算余弦相似度。若相似度超过预设阈值例如 0.92直接返回缓存响应。异步写入与淘汰当大模型生成新的高质量回答后将(Prompt, Embedding, Response)写入 Redis并配置 TTL 自动过期。import Redis from ioredis; export interface SemanticCacheConfig { similarityThreshold: number; // 余弦相似度阈值通常建议 0.90 ~ 0.94 ttlSeconds: number; // 缓存过期时间 maxCandidates: number; // 单次扫描匹配的最大候选条目 } export class SemanticCacheManager { private redis: Redis; private config: SemanticCacheConfig; constructor(redisClient: Redis, config?: PartialSemanticCacheConfig) { this.redis redisClient; this.config { similarityThreshold: config?.similarityThreshold ?? 0.92, ttlSeconds: config?.ttlSeconds ?? 86400, // 默认 1 天 maxCandidates: config?.maxCandidates ?? 50, }; } /** * 计算两个向量的余弦相似度前提向量已做 L2 归一化 */ private cosineSimilarity(vecA: number[], vecB: number[]): number { let dotProduct 0; for (let i 0; i vecA.length; i) { dotProduct vecA[i] * vecB[i]; } return dotProduct; // 已归一化时分母模长乘积为 1 } /** * 将浮点数数组编码为二进制 BufferFloat32Array节省网络与存储开销 */ private vectorToBuffer(vector: number[]): Buffer { const floatArray new Float32Array(vector); return Buffer.from(floatArray.buffer); } /** * 将 Buffer 解码为浮点数数组 */ private bufferToVector(buffer: Buffer): number[] { const floatArray new Float32Array( buffer.buffer, buffer.byteOffset, buffer.byteLength / Float32Array.BYTES_PER_ELEMENT ); return Array.from(floatArray); } /** * 语义检索缓存 */ public async get(queryEmbedding: number[]): Promisestring | null { // 获取当前活跃缓存列表的 key实际生产可结合 Redis HGETALL 或分桶组织 const indexKeys await this.redis.keys(llm:cache:meta:*); if (indexKeys.length 0) { return null; } const limitedKeys indexKeys.slice(0, this.config.maxCandidates); const pipeline this.redis.pipeline(); for (const key of limitedKeys) { pipeline.hgetallBuffer(key); } const results await pipeline.exec(); if (!results) return null; let bestScore -1; let matchedResponse: string | null null; for (const [err, rawHash] of results) { if (err || !rawHash) continue; const hashData rawHash as Recordstring, Buffer; const vecBuf hashData.embedding; const respBuf hashData.response; if (!vecBuf || !respBuf) continue; const cachedVector this.bufferToVector(vecBuf); const similarity this.cosineSimilarity(queryEmbedding, cachedVector); if (similarity bestScore similarity this.config.similarityThreshold) { bestScore similarity; matchedResponse respBuf.toString(utf-8); } } if (matchedResponse) { console.log([SemanticCache] 命中语义缓存相似度: ${bestScore.toFixed(4)}); return matchedResponse; } return null; } /** * 写入语义缓存 */ public async set( prompt: string, queryEmbedding: number[], response: string ): Promisevoid { const id Buffer.from(prompt).toString(base64url).slice(0, 32); const key llm:cache:meta:${id}; const vecBuffer this.vectorToBuffer(queryEmbedding); await this.redis .multi() .hset(key, { prompt: Buffer.from(prompt, utf-8), embedding: vecBuffer, response: Buffer.from(response, utf-8), }) .expire(key, this.config.ttlSeconds) .exec(); } }Redis 原生向量索引RediSearch / Redis Stack演进当数据规模从几千条增长至数万条甚至数十万条时基于 Node.js 内存或管道遍历候选集的开销会逐渐变大。若你的 Redis 服务支持 RediSearchRedis Stack可以直接利用原生HNSW或FLAT向量索引进行近邻查询KNN查询性能可以直接下探到 5ms 以内。创建向量索引的命令如下FT.CREATE idx_llm_cache ON HASH PREFIX 1 llm:cache:meta: SCHEMA \ prompt TEXT NOINDEX \ response TEXT NOINDEX \ embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE通过 RediSearch 执行 KNN 语义检索的查询语句FT.SEARCH idx_llm_cache *[KNN 1 embedding $vec_blob AS score] \ PARAMS 2 vec_blob \x00\x00... \ SORTBY score ASC \ DIALECT 2生产落地的参数控制与成本核算在搭建与运维这套分布式语义缓存架构时有几个具体的参数指标和工程权衡必须把控好余弦相似度阈值的设定阈值过高如0.98几乎退化为纯文本 Hash 匹配语义缓存命中率不足 5%阈值过低如0.85容易发生“答非所问”。例如用户问“如何重启 MySQL”缓存误返回了“如何重启 Redis”的答案。经过我们线上生产环境数十万次请求的样本校准对于 1536 维度的通用 Embedding将阈值固定在0.915 ~ 0.930区间是兼顾准确率与召回率的最佳平衡点。存储压缩与内存占用单个 1536 维度的 Float32 向量序列化为二进制后仅占1536 * 4 6144字节约 6KB。存入 10 万条高频问答纯向量数据仅占约 600MB 内存常规规格的 Redis 实例完全可以轻松承载。ROI 账本收益在多客服智能问答和开发平台文档检索场景中语义缓存使 LLM 上游 API 调用量直接下降了37%。平均响应耗时由大模型输出的 1200ms 降低至缓存命中的 15ms极大提升了终端用户的流畅度。不堆砌重型框架依托基础组件挖掘技术纵深这是保持系统简洁与高可用性的最佳实践。