LLM服务缓存与连接池管理:三层缓存架构与ChatClient池化

LLM服务缓存与连接池管理:三层缓存架构与ChatClient池化

LLM 服务的调用成本远高于传统服务------单次调用延迟 1-10 秒,费用 $0.01-0.1。传统 Web 服务的缓存体系(基于精确键匹配)在 LLM 场景下部分失效,因为用户的输入是自然语言,同样的语义可以用无数种表述方式。

今天聊聊我在 nvc-guide里做的三层缓存架构,以及 ChatClient 的池化管理。

三层缓存架构

LLM 服务的缓存分为三个层次,每层解决不同的问题:

复制代码
用户请求
  │
  ▼
Layer 1: 语义缓存(LLM 响应级)
  │  · pgvector cosine similarity >= 0.95
  │  · 命中率 30-40%,直接跳过 LLM 调用
  │ 未命中
  ▼
Layer 2: 工具缓存(工具结果级)
  │  · ConcurrentHashMap 内存缓存
  │  · TTL 5-30 分钟
  ▼
Layer 3: 配置缓存(Agent 配置级)
  · Redis Cache-Aside,TTL 5 分钟

Layer 1:语义缓存

语义缓存是 LLM 服务特有的缓存形式。核心思路是将用户查询通过 Embedding 模型转换为高维向量,用向量相似度替代字符串精确匹配。

"怎么做 NVC 倾听" 和 "NVC 倾听的步骤是什么" 语义完全相同,但字符串完全不同。传统精确匹配完全失效,语义缓存可以命中。

相似度阈值:设为 0.95。阈值过高(>0.98)命中率极低;阈值过低(<0.90)容易误命中------"NVC 的四个步骤"和"NVC 的四个要素"答案完全不同。宁可少命中也不能答错。

场景白名单:只缓存知识问答类场景(7 种 Coach),不缓存评估和反思------这些任务的输出高度依赖用户的具体表现。

异常降级:所有缓存操作都 try-catch 包裹,任何异常都降级为"缓存未命中",不影响主流程。

Layer 2:工具缓存

Agent 调用的工具(RAG 检索、用户档案查询等)结果在短时间内不会变化。工具缓存与语义缓存的关键区别是:工具输入是结构化的 JSON 参数,可以用精确匹配,不需要语义相似度。

两种缓存 key 策略:

  • 用户级{toolName}:{userId},适用于同一用户结果相同的场景(仪表盘、档案)
  • 参数级{toolName}:{userId}:{paramHash},适用于结果依赖具体参数的场景(RAG、Wiki)

分层 TTL:dashboard 5 分钟、profile 10 分钟、rag_search 30 分钟、wiki_search 30 分钟。

为什么用 JVM 内存而不是 Redis? 工具缓存在 LLM 推理的关键路径上,JVM 内存访问 0.001ms vs Redis 网络往返 1-2ms,差距 1000 倍。短 TTL、高频访问、允许重启后丢失------完全适合内存缓存。

只缓存成功结果。工具调用失败(如数据库超时)时不缓存错误,避免后续请求持续返回错误。

Layer 3:配置缓存

Agent 配置(system prompt、模型参数、温度等)存储在数据库中,但每次 LLM 调用都需要读取。用 Cache-Aside 模式:先查 Redis,miss 则查 DB 并写入 Redis(TTL 5 分钟)。

热更新机制:更新配置时先写 DB,再主动删除 Redis key,保证下次读取回源到最新数据。

java 复制代码
public NvcAgentConfigEntity updateConfig(NvcAgentScene scene, AgentConfigUpdateRequest request) {
    NvcAgentConfigEntity saved = agentConfigRepository.save(config);
    redisService.delete(CACHE_KEY_PREFIX + scene.name());  // 主动清除缓存
    return saved;
}

三层缓存对比

维度 语义缓存 工具缓存 配置缓存
存储介质 pgvector JVM 内存 Redis
匹配方式 向量相似度 精确匹配 精确匹配
典型 TTL 30 天 5-30 分钟 5 分钟
典型命中率 30-40% 60-80% 99%+

缓存带来的收益

指标 无缓存 有缓存 改善
LLM 调用次数/天 ~1000 ~600-700 降低 30-40%
工具调用平均延迟 300ms 60ms 降低 80%
配置读取延迟 5ms 0.5ms 降低 90%
缓存命中响应耗时 2-10s ~55ms 提速 40-180 倍

ChatClient 连接池管理

为什么需要"连接池"

传统连接池(如 HikariCP)管理的是 TCP 长连接的复用。LLM 服务场景的瓶颈不同------ChatClient 对象本身的构建成本很高

成本项 说明
OpenAiChatModel 构建 创建 OpenAiApi、ChatOptions 等
Advisor 链组装 ToolCallAdvisor、MemoryAdvisor 等逐一实例化
Tool 注册 Function Calling 的 ToolCallback 绑定
EmbeddingModel 构建 独立的 OpenAiApi 实例和维度配置

如果每次请求都 new ChatClient(...),会产生大量重复的对象创建和 GC 压力。

ConcurrentHashMap + computeIfAbsent

对于"一旦创建就长期有效"的缓存场景,ConcurrentHashMap.computeIfAbsent 是最优选择:

java 复制代码
private final Map<String, ChatClient> clientCache = new ConcurrentHashMap<>();

public ChatClient getChatClient(String providerId) {
    return clientCache.computeIfAbsent(providerId, id -> createChatClient(id));
}

为什么不用 Guava Cache / Caffeine?Provider 数量有限(3-5 个),不需要 LRU 淘汰和 TTL。ConcurrentHashMap 零依赖,天然适合。

多类型 Client 共存

同一个 Provider 可能需要多种类型的 ChatClient。通过在 key 上加后缀区分:

复制代码
"dashscope"         → 默认 ChatClient(带 Memory + ToolCall + SafeGuard)
"dashscope:plain"   → 纯净 ChatClient(仅 SafeGuard,无工具)
"dashscope:voice"   → 语音 ChatClient(ToolCall,无 Memory)

三种类型互不干扰,各自独立缓存和复用。

ChatClient 是无状态的

ChatClient 本身是无状态的模板对象 ,包含的是 Model 配置 + Advisor 链 + Tool 绑定。对话上下文(ChatMemory)由 MessageChatMemoryAdvisor 在每次请求时从独立的 ChatMemory 存储中加载/保存,按 conversationId 隔离。

多个用户同时使用同一个缓存的 ChatClient 实例,各自的对话历史互不干扰。

配置热更新

ChatClient 缓存一般是 write-once, read-many 模式。需要清空缓存的场景:管理员修改了 Provider 的 API Key 或模型配置、切换了默认 Provider。

java 复制代码
public void reload() {
    clientCache.clear();
    embeddingModelCache.clear();
}

clear() 后,下次访问时自动懒加载重建。已经持有旧 ChatClient 引用的请求不会受影响------旧对象仍然可用直到请求结束。

EmbeddingModel 防误配

EmbeddingModel 也用同样的 ConcurrentHashMap 缓存策略。内置了防误配校验------如果用户把 Chat 模型名(如 glm-4)填到了 Embedding Model 配置,会抛出明确的错误提示。

小结

LLM 服务缓存的核心思路是分层:语义缓存处理 LLM 响应(向量相似度匹配),工具缓存处理 Function Calling 结果(精确 key 匹配),配置缓存处理 Agent 配置(Redis Cache-Aside)。每层的存储、匹配方式、TTL 都不同。

ChatClient 管理的核心是池化:ConcurrentHashMap 缓存已构建的实例,按 Provider + 类型后缀区分,配置变更时 clear + 懒加载重建。

三层缓存整体将 LLM 调用成本降低了 30-40%,工具调用延迟降低了 80%。

相关推荐
CodeBlog-star1 小时前
AI Agent 高并发实战:Redis 限流、队列、缓存与高可用全栈方案
数据库·redis·缓存
万岳科技系统开发1 小时前
医疗诊所小程序如何实现患者管理和会员运营?
java·小程序·apache
小当家.1051 小时前
Token成本控制实战:语义缓存、上下文压缩与工具缓存
java·spring·缓存·token·上下文
乐观勇敢坚强的老彭1 小时前
C++ STL 常用容器的速查表
java·c++·算法
人间凡尔赛1 小时前
当 AI Agent 攻陷网关:Istio agentgateway 与 Gateway API Inference Extension 实战解析
后端·云原生·架构
wwwzhouzy1 小时前
SpringBoot 响应式编程
java·spring boot·后端·响应式编程
DO_Community1 小时前
AI 应用成本怎么算?从推理费用到完整 TCO 拆解
人工智能·gpt·agent·ai编程·llama·kimi
Python私教1 小时前
请求超时不等于失败:把写操作恢复设计成事实回读
后端·python
迷路爸爸1801 小时前
Claude Code 核心设计学习记录
学习·microsoft·agent·智能体·multi-agent·claude code