P7的终极答案------服务端会话 + 分层存储
前面四层都是"术",这一层是"道"。上下文管理根本不该前端管,让后端接管会话。配合前端热/温/冷三层存储架构,构建永不崩溃的AI应用。
01. 核心思想转移
在第五层,我们要打破思维定式:前端只负责"展示",不负责"记忆"。
- 错误做法 :前端维护完整
messages数组,每次请求全量带上。 - 正确做法:前端只存消息ID、角色、内容片段;后端维护完整的上下文窗口、摘要、向量索引。
02. 服务端会话管理架构
交互流程:
- 用户发送消息,请求体仅包含:
{ sessionId, content, lastN: 10 }。 - 后端根据
sessionId从Redis取出完整的上下文窗口(或摘要)。 - 后端将
content拼入上下文,调用大模型。 - 流式返回时,每推送一个chunk,同时异步存入Redis和数据库。
typescript
// 后端伪代码(Node.js)
app.post('/api/chat', async (req, res) => {
const { sessionId, content } = req.body;
// 1. 从Redis拉取上下文摘要或最近N轮
let context = await redis.get(`chat:${sessionId}:context`);
if (!context) {
// 从数据库拉取最近20条,并生成摘要
const history = await db.message.findMany({ where: { sessionId }, orderBy: { createdAt: 'desc' }, take: 20 });
context = buildContext(history);
await redis.setex(`chat:${sessionId}:context`, 3600, context);
}
// 2. 组装Prompt
const messages = [
{ role: 'system', content: context.summary },
...context.recent,
{ role: 'user', content }
];
// 3. 流式请求大模型
const stream = await openai.chat.completions.create({ model: 'gpt-4', messages, stream: true });
// 4. 响应流 + 异步保存
let fullContent = '';
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content || '';
fullContent += delta;
res.write(`data: ${JSON.stringify({ id: chunk.id, content: delta })}\n\n`);
}
// 5. 保存到数据库,并异步更新Redis摘要(触发摘要压缩任务)
await saveMessage(sessionId, { role: 'assistant', content: fullContent });
await updateContextSummary(sessionId); // 后台任务
});
03. 前端热/温/冷三层存储
把前端存储想象成CPU的三级缓存:
| 层级 | 数据范围 | 存储介质 | 读取速度 | 更新频率 |
|---|---|---|---|---|
| L1 热 | 最近50条 | 内存(Valtio/Zustand) | 纳秒级 | 实时流式更新 |
| L2 温 | 51-200条 | IndexedDB | 毫秒级 | 滚动加载时读取 |
| L3 冷 | 200条以上 | 服务端API | 百毫秒级 | 翻页/搜索时读取 |
具体实现策略:
- 用户进入会话,加载最近50条 → 存入L1,同时在后台预加载51-200条存入L2(但不渲染)。
- 用户向上滚动到接近顶部(如第45条可见时),触发L2数据从IndexedDB取出插入到L1后面(或者直接扩展L1渲染范围)。
- 如果用户继续向上翻到第150条,但IndexedDB只有200条,则触发API请求L3冷数据。
typescript
// 滚动加载逻辑结合分层存储
const loadMoreMessages = async (direction: 'up') => {
if (direction === 'up' && currentPage === 1) {
// 1. 尝试从IndexedDB读温数据
const warmData = await db.messages.where('sessionId').equals(sessionId)
.offset(50).limit(150).toArray();
if (warmData.length > 0) {
setMessages(prev => [...warmData, ...prev]);
return;
}
// 2. 温数据没有则请求冷数据API
const coldData = await fetchMessages(sessionId, { page: 2, limit: 100 });
setMessages(prev => [...coldData, ...prev]);
// 存一份到IndexedDB供下次快速读取
await db.messages.bulkPut(coldData);
}
};
04. 监控与告警体系(Sentry + 自定义)
作为P7,必须为系统装上"神经末梢"。
- 接入Sentry :监控
Performance中的内存占用趋势图,监听beforeunload上报页面崩溃(通过navigator.sendBeacon)。 - 自定义阈值告警:
typescript
// 监控循环(每30秒检查一次)
setInterval(() => {
const mem = (performance as any).memory?.usedJSHeapSize;
if (mem && mem > 500 * 1024 * 1024) { // 超过500MB
// 上报告警
Sentry.captureMessage('内存占用过高,触发自动清理', { level: 'warning' });
// 执行自动清理:保留最近500条 + 摘要
const summary = generateSummary(messages.slice(0, -100));
setMessages(prev => [...prev.slice(-500), { role: 'system', content: `[历史摘要]: ${summary}` }]);
}
}, 30000);
📖 下一篇预告(第06篇)
复盘------从"清空列表"到"架构师思维"
场景预告:从"清空列表"到"架构师思维":AI对话应用的五层性能突围。
核心看点:在本系列的最终篇,我们将跳出具体的代码实现,为你奉上一张全维度的技术决策地图。这不仅是一篇复盘,更是一次从初中级工程师迈向 P7+ 架构师的思维跃迁。