AI对话前端从入门到崩溃:一个长对话引发的五层优化战争【五】

0 阅读2分钟

P7的终极答案——服务端会话 + 分层存储

前面四层都是“术”,这一层是“道”。上下文管理根本不该前端管,让后端接管会话。配合前端热/温/冷三层存储架构,构建永不崩溃的AI应用。

01. 核心思想转移

在第五层,我们要打破思维定式:前端只负责“展示”,不负责“记忆”

  • 错误做法:前端维护完整messages数组,每次请求全量带上。
  • 正确做法:前端只存消息ID、角色、内容片段;后端维护完整的上下文窗口、摘要、向量索引。

02. 服务端会话管理架构

交互流程

  1. 用户发送消息,请求体仅包含:{ sessionId, content, lastN: 10 }
  2. 后端根据sessionId从Redis取出完整的上下文窗口(或摘要)。
  3. 后端将content拼入上下文,调用大模型。
  4. 流式返回时,每推送一个chunk,同时异步存入Redis和数据库。
// 后端伪代码(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冷数据。
// 滚动加载逻辑结合分层存储
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)。
  • 自定义阈值告警
// 监控循环(每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+ 架构师的思维跃迁。