2025年3月

前情回顾
点我回顾前景
在很久很久之前开发了一个ai客户端,已经好久没搞过了都有点忘记写了点什么了...
某天在用的时候,发现一个问题,由于上下文越来越长,每次切换对话的加载时间也越来越长
作为高贵的英专生,我们学习的每一分每一秒都是十分珍贵的
我不允许这样的事情发生,于是我便想到可以用redis来解决这一难题
先介绍一下redis是什么,这里借用知乎的一篇文章
点我去知乎!
Redis,英文全称是Remote Dictionary Server(远程字典服务),是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度非常快,每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存,另外,Redis也经常用来做分布式锁。除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。

也就是说我们可以把mysql中的数据拿出来存在内存中,这样就可以大大加快读取速度

export async function GET(
  request: NextRequest,
  { params }: { params: { chatId: string } }
) {
  const session = await getServerSession(authOptions)
  if (!session?.user) return new Response("Unauthorized", { status: 401 })

  const startTime = Date.now();
  
  try {
    const redis = redisManager.getClient();
    
    const searchParams = request.nextUrl.searchParams
    const limit = parseInt(searchParams.get('limit') || '20')
    const before = searchParams.get('before')
    const forceRefresh = searchParams.get('forceRefresh') === 'true'
    
    
    const useCache = !before && isRedisConnected() && !forceRefresh
    const cacheKey = `messages:${params.chatId}:${session.user.id}`
    
    console.log(`[GET_MESSAGES] chatId=${params.chatId}, useCache=${useCache}, forceRefresh=${forceRefresh}`)
    
    let responseData;
    let retryCount = 0;
    const maxRetries = 3;
    
    while (retryCount < maxRetries) {
      try {
        
        if (useCache) {
          const cachedData = await redis.get(cacheKey)
          if (cachedData) {
            try {
              const parsedData = JSON.parse(cachedData)
              
              if (parsedData.messages && 
                  Array.isArray(parsedData.messages) && 
                  parsedData.messages.length > 0 &&
                  parsedData.messages.every((msg: { id: string; content: string | null; role: string; }) => 
                    msg.id && msg.content !== null && msg.role
                  ) && 
                  parsedData.timestamp && 
                  Date.now() - parsedData.timestamp < 300000) {
                
                const elapsedTime = Date.now() - startTime;
                console.log(`[CACHE_HIT] Using cached messages for ${params.chatId} (${parsedData.messages.length} messages) in ${elapsedTime}ms`)
                
                responseData = parsedData;
                break;
              } else {
                console.log(`[CACHE_INVALID] Invalid or incomplete cache data for ${params.chatId}`)
              }
            } catch (err) {
              console.error("[CACHE_ERROR] Invalid cache data:", err)
            }
          }
        }
        
        
        
        const dbStartTime = Date.now();
        
        
        let whereCondition: any = { 
          chatId: params.chatId,
          userId: session.user.id
        }
        
        if (before) {
          const beforeMessage = await prisma.message.findUnique({
            where: { id: before },
            select: { createdAt: true }
          })
          
          if (beforeMessage) {
            whereCondition.createdAt = {
              lt: beforeMessage.createdAt
            }
          }
        }
        
        const messages = await prisma.message.findMany({
          where: whereCondition,
          select: {
            id: true,
            content: true,
            role: true,
            createdAt: true,
            updatedAt: true,
            images: true,
            model: true,
            reasoning_content: true,
            sourceDocs: true,
            thinking_start_time: true,
            thinking_end_time: true
          },
          orderBy: { createdAt: 'desc' },
          take: limit + 1
        })
        
        if (messages.length > 0) {
          const hasMore = messages.length > limit
          const responseMessages = messages.slice(0, limit).reverse()
          
          responseData = {
            messages: responseMessages,
            hasMore: hasMore,
            timestamp: Date.now()
          }
          
          const dbElapsedTime = Date.now() - dbStartTime;
          console.log(`[DB_FETCH] Fetched ${responseMessages.length} messages from DB for ${params.chatId} in ${dbElapsedTime}ms`)
          
          
          if (isRedisConnected() && !before) {
            await redis.set(
              cacheKey, 
              JSON.stringify(responseData), 
              'EX', 
              300 // 5分钟过期
            );
            console.log(`[CACHE_UPDATE] Updated cache for ${params.chatId} with ${responseMessages.length} messages`)
          }
          break;
        } else {
          console.log(`[DB_FETCH] No messages found in database for ${params.chatId}`)
        }
        
        retryCount++;
        if (retryCount < maxRetries) {
          console.log(`[RETRY] Attempt ${retryCount + 1} of ${maxRetries} for ${params.chatId}`);
          await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
        }
      } catch (error) {
        console.error(`[ERROR] Attempt ${retryCount + 1} failed:`, error);
        retryCount++;
        if (retryCount < maxRetries) {
          await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
        }
      }
    }
    
    
    if (!responseData || !responseData.messages) {
      console.log(`[NO_MESSAGES] No messages found for ${params.chatId}`);
      return new Response(
        JSON.stringify({ 
          messages: [], 
          hasMore: false,
          timestamp: Date.now()
        }),
        { 
          status: 200,
          headers: { 'Content-Type': 'application/json' }
        }
      );
    }
    
    const totalElapsedTime = Date.now() - startTime;
    
    
    return new Response(JSON.stringify(responseData), {
      headers: {
        'Content-Type': 'application/json',
        'Cache-Control': 'no-store',
        'X-Cache': responseData ? 'HIT' : 'MISS',
        'X-Response-Time': `${totalElapsedTime}ms`
      }
    })
  } catch (error) {
    const errorTime = Date.now() - startTime;
    console.error(`[GET_MESSAGES] Error in ${errorTime}ms:`, error)
    return new Response("Internal Error", { status: 500 })
  }
}

初始化一个redis客户端,并加入这一串神秘代码后,便成功了,在加载完一次后,很长一段时间不会再进行加载,这样就不会切换对话导致加载半天了