分布式上下文存储与同步:长对话的跨节点状态管理
核心论点:LLM 的上下文窗口是硬上限,而多节点部署下"对话状态在哪"直接决定请求打到哪台机器;用 Redis 按会话 ID 分片存储 + 截断策略,把有限窗口变成可跨节点复用的共享状态。
问题定义:上下文窗口有限 + 多节点不一致
LLM 一次能"看到"的 token(模型处理文本的基本单位)数有上限(视模型从几 K 到上百 K 不等)。但客服对话可能持续 50 轮,累计远超窗口。更棘手的是分布式:用户上一轮打在节点 A,这一轮被负载均衡到节点 B——如果上下文只存在 A 的内存里,B 就是"失忆"的。所以上下文不能绑在进程内存,必须外置成共享存储。
这就引出三个独立问题:存哪(选型)、怎么同步(一致性)、请求打给谁(路由)。
分布式上下文存储选型
- Redis:毫秒级读写,原生 TTL 过期,最适合"当前对话的近期消息"。代价是无结构化查询,长文本检索弱。
- MongoDB:文档模型存完整对话,适合需要回查历史的审计/复盘场景,但延迟高于 Redis。
- 向量数据库(如 Milvus):把历史转 embedding(把文本转成一串数字向量,语义相近的向量距离也近)做语义召回,适合"从长对话里捞相关片段",但每次写入都要额外算一遍向量、成本高。
Shop-Agent 选Redis 作为主存储:对话历史按会话 ID 分片存储,每条消息落库前截断到 4096 字符防撑爆 Redis,整体 24 小时过期(当前为固定值,尚未提为配置项)。这是"近期上下文高频访问"特征下的最优选。
这三者并非严格互斥——Redis 自身也支持向量相似度搜索,"近期存储"与"语义召回"可以落在同一套基础设施上。上面的决策图是按主导访问特征划分,不是排他选型。
这里的存储选型针对对话历史(低延迟 + 可过期 + 窗口连续),与 RAG 检索的知识文档选型是两件事——前者保"聊到哪了",后者保"知识召回",虽都可用向量库,但问题不同,勿混为一谈。
跨节点上下文同步
选型定了,同步要解决"多节点看到同一份"。Redis 本身是中心节点,所有节点读写同一份,天然规避了"内存副本漂移"。但仍有两点必须处理:
- 写入版本/时序:并发写同一对话(如用户连发两条)需靠 Redis 原子操作或单写者模型,避免后写覆盖先写。
- 增量更新 vs 全量覆盖:只追加新消息而非每次重写整个历史,减少跨节点带宽与冲突面。落地上用一次流水线里的两个原子操作:尾部追加新消息 + 头部裁剪只保留最近 N 条,而非全量覆盖。
- 事件驱动(演进方向):上下文变更可经发布/订阅广播,触发压缩、摘要等下游动作;当前 Shop-Agent 主要靠共享存储 + 取用时裁剪保证一致,事件驱动留作扩展。
上下文路由:把请求打到"有状态"的节点
选型定了、同步也兜底了,还剩第三个问题:请求到底打给哪台节点。如果状态在共享 Redis,其实任意节点都能接——这是 Shop-Agent 的做法,靠外部存储解耦,不需要"粘滞会话(sticky session)"。负载均衡可用任意无状态策略(如轮询),因为节点间无状态差异。但代价是每次请求都要从 Redis 拉历史。
拉取时再做一次窗口裁剪:prompt 构建阶段从 Redis 取历史,按配置的最大历史轮数保留,单条按可配置上限截断(单条长度、默认值等具体窗口策略见《上下文压缩与窗口管理》)。这把"无限历史"压成"窗口内的有效上下文",既控 token 又保业务连贯。
三个问题各自的答案已经清楚,把它们拼到一张图上看整体形态。
实战:Shop-Agent 的对话历史存储架构
要点:状态外置到 Redis,节点无状态可任意接管;每条消息落库前截断 4096 字符、整体 24 小时(当前为固定值,尚未提为配置项)过期;取用时再按轮数 + 单条可配置上限裁剪进窗口。
核心要点
- 存储选型不是排他三选一:Redis 自身也支持向量检索,近期存储与语义召回可共用一套设施,决策图按主导访问特征划分。
- 上下文不能绑进程内存,否则多节点部署必失忆;必须外置为共享存储。
- 共享 Redis 让任意节点可接管,免去 sticky session,但每次需拉历史并裁剪窗口。
- Shop-Agent:按会话 ID 分片、单条截断 4096 字、24 小时固定过期,取用时再限轮数 + 单条按可配置上限裁剪;写入用增量追加而非全量覆盖。
- 路由与同步解耦后,上下文一致性问题退化为"Redis 可用 + 裁剪策略正确"。
