gongxiangp2/docs/upload-sessions-comparison.md

209 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 上传会话存储方案对比
## 🔍 快速对比
### 场景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)
```typescript
// lib/upload-sessions.ts
export const uploadSessions = new Map<string, UploadSession>()
// 使用
uploadSessions.set(uploadId, session)
const session = uploadSessions.get(uploadId)
```
### 迁移后(Redis)
```typescript
// lib/upload-sessions.ts
import { saveUploadSession, getUploadSession } from './upload-sessions-redis'
// 使用(接口相同)
await saveUploadSession(uploadId, session)
const session = await getUploadSession(uploadId)
```
### 适配层(平滑迁移)
```typescript
// 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. **实现适配层**(可以平滑迁移)
---
## 📚 相关文档
- [详细说明](./upload-sessions-redis-explanation.md)
- [Redis实现示例](../lib/upload-sessions-redis.ts.example)
- [Redis官方文档](https://redis.io/docs/)