gongxiangp2/docs/upload-sessions-comparison.md

5.9 KiB
Raw Blame History

上传会话存储方案对比

🔍 快速对比

场景1:单实例部署(当前情况)

┌─────────────────┐
│   Docker容器    │
│   ┌───────────┐ │
│   │ Next.js   │ │
│   │ 内存Map   │ │ ← 可以工作 ✅
│   └───────────┘ │
└─────────────────┘

用户请求 → 同一个容器 → 内存Map中有数据 → 正常工作

场景2:多实例部署(未来可能)

        ┌──────────────┐
        │  负载均衡器   │
        └──────┬───────┘
               │
       ┌───────┴───────┐
       │               │
  ┌────▼───┐      ┌───▼────┐
  │容器1   │      │容器2   │
  │内存Map │      │内存Map │
  └────────┘      └────────┘

用户请求1 → 容器1 → 初始化上传 → 数据存在容器1内存
用户请求2 → 容器2 → 上传块 → 容器2找不到数据 → 失败 ❌

场景3:使用Redis(多实例)

        ┌──────────────┐
        │  负载均衡器   │
        └──────┬───────┘
               │
       ┌───────┴───────┐
       │               │
  ┌────▼───┐      ┌───▼────┐
  │容器1   │      │容器2   │
  └────┬───┘      └───┬────┘
       │              │
       └──────┬───────┘
              │
        ┌─────▼─────┐
        │  Redis    │ ← 共享存储 ✅
        └───────────┘

用户请求1 → 容器1 → 初始化上传 → 数据存在Redis
用户请求2 → 容器2 → 上传块 → 从Redis读取数据 → 正常工作 ✅

📊 详细对比表

特性 内存Map Redis
数据存储位置 进程内存 独立内存(可持久化)
数据持久化 ❌ 进程重启丢失 ✅ 可配置RDB/AOF
多实例支持 ❌ 每个实例独立 ✅ 全局共享
自动过期 ❌ 需手动清理 ✅ 支持TTL
部署复杂度 ✅ 简单(无需额外服务) ❌ 需要Redis服务
性能 ✅ 极快(本地内存访问) ✅ 很快(网络内存访问)
内存占用 ✅ 小(应用内存) ❌ 额外进程(~10-50MB)
高可用性 ❌ 单点故障 ✅ 支持主从、集群
跨服务共享 ❌ 无法跨进程 ✅ 支持跨服务
适用场景 单实例、开发测试 生产环境、多实例

🎯 实际案例

案例1:服务器重启

时间线:
10:00 - 用户开始上传1GB文件
10:05 - 上传进度50%
10:10 - 服务器重启(部署更新)

内存Map方案:
- 上传会话丢失 ❌
- 用户需要重新开始上传
- 断点续传功能失效

Redis方案:
- 上传会话保存在Redis(如果配置了持久化)
- 服务器重启后,会话数据仍然存在
- 用户可以从50%继续上传 ✅

案例2:多实例部署

场景:使用Nginx负载均衡,2个容器实例

用户上传流程:
1. 初始化上传 → 请求路由到容器1 → 会话存在容器1内存
2. 上传块1 → 请求路由到容器2 → 容器2找不到会话 → 失败 ❌

Redis方案:
1. 初始化上传 → 请求路由到容器1 → 会话存在Redis
2. 上传块1 → 请求路由到容器2 → 从Redis读取会话 → 成功 ✅

案例3:内存泄漏

内存Map方案:
- 如果清理函数失败或未执行
- 内存会持续增长
- 最终导致OOM(内存溢出)

Redis方案:
- 使用TTL自动过期
- 即使应用崩溃,Redis也会自动清理
- 内存管理更可靠

💡 迁移建议

阶段1:当前(单实例)

  • ✅ 继续使用内存Map
  • ✅ 简单、快速、无需额外服务
  • ✅ 适合当前部署方式

阶段2:扩展(多实例)

  • 🔄 迁移到Redis
  • 🔄 支持负载均衡
  • 🔄 支持高可用性

阶段3:优化(生产环境)

  • 🚀 Redis集群
  • 🚀 主从复制
  • 🚀 自动故障转移

📝 代码迁移示例

当前代码(内存Map)

// lib/upload-sessions.ts
export const uploadSessions = new Map<string, UploadSession>()

// 使用
uploadSessions.set(uploadId, session)
const session = uploadSessions.get(uploadId)

迁移后(Redis)

// lib/upload-sessions.ts
import { saveUploadSession, getUploadSession } from './upload-sessions-redis'

// 使用(接口相同)
await saveUploadSession(uploadId, session)
const session = await getUploadSession(uploadId)

适配层(平滑迁移)

// lib/upload-sessions-adapter.ts
const USE_REDIS = process.env.USE_REDIS === 'true'

export async function saveSession(uploadId: string, session: UploadSession) {
  if (USE_REDIS) {
    return saveUploadSessionRedis(uploadId, session)
  } else {
    uploadSessionsMemory.set(uploadId, session)
  }
}

🚀 总结

什么时候用内存Map?

  • ✅ 单实例部署
  • ✅ 开发/测试环境
  • ✅ 小规模使用
  • ✅ 简单部署

什么时候用Redis?

  • ✅ 多实例部署
  • ✅ 生产环境
  • ✅ 高可用性要求
  • ✅ 数据持久化需求
  • ✅ 大量并发上传

当前建议

  1. 继续使用内存Map(当前单实例部署可以工作)
  2. 准备Redis方案(为未来扩展做准备)
  3. 实现适配层(可以平滑迁移)

📚 相关文档