209 lines
5.9 KiB
Markdown
209 lines
5.9 KiB
Markdown
# 上传会话存储方案对比
|
||
|
||
## 🔍 快速对比
|
||
|
||
### 场景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/)
|
||
|