feat: chunk file upload add quest pool
This commit is contained in:
+421
-12
@@ -6,7 +6,7 @@ V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建
|
||||
|
||||
## 主要特性
|
||||
|
||||
- ✅ **分片上传**:支持大文件分片上传,默认分片大小 2MB
|
||||
- ✅ **分片上传**:支持大文件分片上传,可配置分片大小
|
||||
- ✅ **断点续传**:支持上传中断后继续上传
|
||||
- ✅ **秒传功能**:基于文件哈希的秒传检测
|
||||
- ✅ **自动合并**:所有分片上传完成后自动合并文件
|
||||
@@ -14,12 +14,17 @@ V-Hono 大文件分片上传系统是一个基于 V 语言和 Hono 框架构建
|
||||
- ✅ **数据库存储**:使用 SQLite 存储文件元数据
|
||||
- ✅ **RESTful API**:完整的 REST API 接口
|
||||
- ✅ **Web 界面**:提供友好的 Web 上传界面
|
||||
- ✅ **可配置参数**:支持自定义分片大小、文件大小限制等
|
||||
- ✅ **双请求池系统**:活跃请求池 + 待请求池,智能并发控制
|
||||
- ✅ **实时状态监控**:实时显示上传进度和请求池状态
|
||||
|
||||
## 系统架构
|
||||
|
||||
```
|
||||
前端 (Web/移动端)
|
||||
↓
|
||||
双请求池系统 (活跃池 + 待请求池)
|
||||
↓
|
||||
V-Hono 服务器
|
||||
↓
|
||||
分片存储 (./uploads/chunks/)
|
||||
@@ -29,6 +34,27 @@ V-Hono 服务器
|
||||
数据库 (SQLite)
|
||||
```
|
||||
|
||||
### 双请求池系统架构
|
||||
|
||||
```
|
||||
请求池管理器
|
||||
├── 活跃请求池 (Map<requestId, Promise>)
|
||||
│ ├── 请求1 (正在执行)
|
||||
│ ├── 请求2 (正在执行)
|
||||
│ └── ... (最多6个并发)
|
||||
└── 待请求池 (Array<Request>)
|
||||
├── 请求7 (等待执行)
|
||||
├── 请求8 (等待执行)
|
||||
└── ... (无限制)
|
||||
```
|
||||
|
||||
**工作原理:**
|
||||
1. 新请求到达时,检查活跃池是否已满
|
||||
2. 如果未满,直接加入活跃池并执行
|
||||
3. 如果已满,加入待请求池等待
|
||||
4. 活跃池中的请求完成后,自动从待请求池取出下一个请求执行
|
||||
5. 实时更新状态显示,提供完整的监控信息
|
||||
|
||||
## 安装和运行
|
||||
|
||||
### 1. 环境要求
|
||||
@@ -170,6 +196,7 @@ v run chunk_upload_example.v
|
||||
pub struct ChunkUploadConfig {
|
||||
chunk_size: int = 1024 * 1024 // 默认分片大小 1MB
|
||||
max_file_size: int = 1024 * 1024 * 1024 // 最大文件大小 1GB
|
||||
max_chunk_size: int = 10 * 1024 * 1024 // 最大分片大小 10MB
|
||||
temp_dir: string = './uploads/chunks' // 临时分片目录
|
||||
upload_dir: string = './uploads/files' // 最终文件目录
|
||||
cleanup_delay: int = 3600 // 清理延迟时间(秒)
|
||||
@@ -194,11 +221,208 @@ uploads/
|
||||
└── files.db # SQLite 数据库文件
|
||||
```
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 1. 使用默认配置
|
||||
|
||||
```v
|
||||
// 使用默认配置创建分片上传管理器
|
||||
mut upload_manager := hono.new_chunk_upload_manager(hono.ChunkUploadConfig{})
|
||||
```
|
||||
|
||||
### 2. 使用自定义配置
|
||||
|
||||
```v
|
||||
// 创建自定义配置
|
||||
custom_config := hono.ChunkUploadConfig{
|
||||
chunk_size: 5 * 1024 * 1024 // 5MB 默认分片大小
|
||||
max_file_size: 5 * 1024 * 1024 * 1024 // 5GB 最大文件大小
|
||||
max_chunk_size: 20 * 1024 * 1024 // 20MB 最大分片大小
|
||||
temp_dir: './uploads/chunks'
|
||||
upload_dir: './uploads/files'
|
||||
cleanup_delay: 7200 // 2小时后清理临时文件
|
||||
clear_chunks_on_complete: true // 上传完成后清理分片
|
||||
db_path: './uploads/files.db'
|
||||
}
|
||||
|
||||
// 使用自定义配置创建分片上传管理器
|
||||
mut upload_manager := hono.new_chunk_upload_manager(custom_config)
|
||||
```
|
||||
|
||||
### 3. 获取配置信息
|
||||
|
||||
前端可以通过 `/api/upload-config` 接口获取后端配置:
|
||||
|
||||
```javascript
|
||||
// 获取后端配置信息
|
||||
async function getUploadConfig() {
|
||||
const response = await fetch('/api/upload-config');
|
||||
const config = await response.json();
|
||||
console.log('最大分片大小:', config.max_chunk_size);
|
||||
return config;
|
||||
}
|
||||
```
|
||||
|
||||
## 双请求池系统详解
|
||||
|
||||
### 概述
|
||||
|
||||
双请求池系统是 V-Hono 分片上传系统的核心性能优化组件,通过智能的并发控制机制,确保大文件上传的高效性和稳定性。
|
||||
|
||||
### 核心组件
|
||||
|
||||
#### 1. 活跃请求池 (Active Pool)
|
||||
- **数据结构**:`Map<requestId, Promise>`
|
||||
- **功能**:存储正在执行的请求
|
||||
- **容量**:可配置,默认最大6个并发
|
||||
- **特点**:实时执行,状态可追踪
|
||||
|
||||
#### 2. 待请求池 (Pending Pool)
|
||||
- **数据结构**:`Array<Request>`
|
||||
- **功能**:存储等待执行的请求
|
||||
- **容量**:无限制,按需扩展
|
||||
- **特点**:FIFO队列,自动调度
|
||||
|
||||
#### 3. 请求管理器
|
||||
- **请求ID生成**:自动递增的唯一标识
|
||||
- **智能调度**:根据活跃池状态自动分配
|
||||
- **状态监控**:实时提供池状态信息
|
||||
- **错误处理**:请求级别的错误隔离
|
||||
|
||||
### 配置参数
|
||||
|
||||
```javascript
|
||||
// 创建请求池实例
|
||||
const requestPool = new RequestPool(6); // 最大并发6个
|
||||
|
||||
// 可配置参数
|
||||
const config = {
|
||||
maxConcurrent: 6, // 最大并发数
|
||||
enableLogging: true, // 启用日志
|
||||
autoRetry: true, // 自动重试
|
||||
retryCount: 3 // 重试次数
|
||||
};
|
||||
```
|
||||
|
||||
### 状态监控
|
||||
|
||||
```javascript
|
||||
// 获取实时状态
|
||||
const status = requestPool.getStatus();
|
||||
console.log({
|
||||
activeCount: status.activeCount, // 活跃请求数
|
||||
pendingCount: status.pendingCount, // 等待请求数
|
||||
maxConcurrent: status.maxConcurrent, // 最大并发数
|
||||
utilization: (status.activeCount / status.maxConcurrent * 100).toFixed(1) + '%' // 利用率
|
||||
});
|
||||
```
|
||||
|
||||
### 性能优势
|
||||
|
||||
1. **并发控制**:避免服务器过载,保护系统稳定性
|
||||
2. **资源优化**:合理利用网络带宽和服务器资源
|
||||
3. **用户体验**:提供实时进度反馈和状态显示
|
||||
4. **错误隔离**:单个请求失败不影响其他请求
|
||||
5. **自动恢复**:支持断点续传和失败重试
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 前端 JavaScript 示例
|
||||
|
||||
```javascript
|
||||
// 请求池管理类
|
||||
class RequestPool {
|
||||
constructor(maxConcurrent = 6) {
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
this.activePool = new Map(); // 活跃请求池 {requestId: Promise}
|
||||
this.pendingPool = []; // 待请求池 [{requestId, task, resolve, reject}]
|
||||
this.requestIdCounter = 0;
|
||||
}
|
||||
|
||||
// 生成请求ID
|
||||
generateRequestId() {
|
||||
return ++this.requestIdCounter;
|
||||
}
|
||||
|
||||
// 添加请求到池中
|
||||
async addRequest(task) {
|
||||
const requestId = this.generateRequestId();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = {
|
||||
requestId,
|
||||
task,
|
||||
resolve,
|
||||
reject
|
||||
};
|
||||
|
||||
// 如果活跃池未满,直接执行
|
||||
if (this.activePool.size < this.maxConcurrent) {
|
||||
this.executeRequest(request);
|
||||
} else {
|
||||
// 否则加入待请求池
|
||||
this.pendingPool.push(request);
|
||||
console.log(`请求 ${requestId} 加入待请求池,当前待请求数: ${this.pendingPool.length}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 执行请求
|
||||
async executeRequest(request) {
|
||||
const { requestId, task, resolve, reject } = request;
|
||||
|
||||
console.log(`开始执行请求 ${requestId},当前活跃请求数: ${this.activePool.size + 1}`);
|
||||
|
||||
// 将请求添加到活跃池
|
||||
const promise = task()
|
||||
.then(result => {
|
||||
console.log(`请求 ${requestId} 执行成功`);
|
||||
resolve(result);
|
||||
return result;
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(`请求 ${requestId} 执行失败:`, error);
|
||||
reject(error);
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
// 请求完成后从活跃池移除
|
||||
this.activePool.delete(requestId);
|
||||
console.log(`请求 ${requestId} 完成,从活跃池移除,当前活跃请求数: ${this.activePool.size}`);
|
||||
|
||||
// 检查待请求池,如果有待请求则执行
|
||||
this.processPendingRequests();
|
||||
});
|
||||
|
||||
this.activePool.set(requestId, promise);
|
||||
}
|
||||
|
||||
// 处理待请求池中的请求
|
||||
processPendingRequests() {
|
||||
while (this.pendingPool.length > 0 && this.activePool.size < this.maxConcurrent) {
|
||||
const request = this.pendingPool.shift();
|
||||
this.executeRequest(request);
|
||||
console.log(`从待请求池取出请求 ${request.requestId} 执行,剩余待请求数: ${this.pendingPool.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取池状态
|
||||
getStatus() {
|
||||
return {
|
||||
activeCount: this.activePool.size,
|
||||
pendingCount: this.pendingPool.length,
|
||||
maxConcurrent: this.maxConcurrent
|
||||
};
|
||||
}
|
||||
|
||||
// 清空所有池
|
||||
clear() {
|
||||
this.activePool.clear();
|
||||
this.pendingPool = [];
|
||||
console.log('请求池已清空');
|
||||
}
|
||||
}
|
||||
|
||||
// 计算文件哈希
|
||||
async function calculateFileHash(file) {
|
||||
return new Promise(resolve => {
|
||||
@@ -238,24 +462,61 @@ async function uploadChunk(file, chunk, { fileHash, chunkIndex, chunkHash }) {
|
||||
return false; // 继续上传
|
||||
}
|
||||
|
||||
// 分片上传主函数
|
||||
async function uploadFile(file) {
|
||||
// 使用请求池的分片上传主函数
|
||||
async function uploadFileWithPool(file) {
|
||||
const fileHash = await calculateFileHash(file);
|
||||
const chunkSize = 2 * 1024 * 1024; // 2MB
|
||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||
|
||||
// 创建请求池实例
|
||||
const requestPool = new RequestPool(6);
|
||||
|
||||
// 创建所有分片的上传任务并添加到请求池
|
||||
const uploadPromises = [];
|
||||
for (let i = 0; i < totalChunks; i++) {
|
||||
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
|
||||
const chunkHash = await calculateFileHash(chunk);
|
||||
|
||||
const completed = await uploadChunk(file, chunk, {
|
||||
fileHash,
|
||||
chunkIndex: i,
|
||||
chunkHash
|
||||
const chunkIndex = i;
|
||||
const promise = requestPool.addRequest(async () => {
|
||||
const chunk = file.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize);
|
||||
const chunkHash = await calculateFileHash(chunk);
|
||||
|
||||
return await uploadChunk(file, chunk, {
|
||||
fileHash,
|
||||
chunkIndex,
|
||||
chunkHash
|
||||
});
|
||||
});
|
||||
|
||||
if (completed) {
|
||||
break; // 上传完成
|
||||
uploadPromises.push(promise);
|
||||
}
|
||||
|
||||
// 等待所有上传任务完成
|
||||
const results = await Promise.all(uploadPromises);
|
||||
|
||||
// 清空请求池
|
||||
requestPool.clear();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 实时监控请求池状态
|
||||
function monitorRequestPool(requestPool) {
|
||||
setInterval(() => {
|
||||
const status = requestPool.getStatus();
|
||||
console.log(`请求池状态: ${status.activeCount}/${status.maxConcurrent} 活跃, ${status.pendingCount} 等待中`);
|
||||
|
||||
// 更新UI显示
|
||||
updatePoolStatusDisplay(status);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 更新UI显示
|
||||
function updatePoolStatusDisplay(status) {
|
||||
const statusElement = document.getElementById('poolStatus');
|
||||
if (statusElement) {
|
||||
if (status.activeCount === 0 && status.pendingCount === 0) {
|
||||
statusElement.textContent = '请求池状态: 所有请求已完成';
|
||||
} else {
|
||||
statusElement.textContent = `请求池状态: ${status.activeCount}/${status.maxConcurrent} 活跃, ${status.pendingCount} 等待中`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,6 +539,154 @@ curl -X POST http://localhost:8080/upload/chunk \
|
||||
curl "http://localhost:8080/upload/chunk_exists?file_hash=abc123&chunk_index=0&chunk_hash=def456&file_size=10485760&trunk_size=2097152"
|
||||
```
|
||||
|
||||
## 请求池最佳实践
|
||||
|
||||
### 1. 并发数配置建议
|
||||
|
||||
```javascript
|
||||
// 根据网络环境调整并发数
|
||||
const networkConfig = {
|
||||
'fast': 8, // 高速网络:8个并发
|
||||
'normal': 6, // 普通网络:6个并发
|
||||
'slow': 4, // 慢速网络:4个并发
|
||||
'mobile': 3 // 移动网络:3个并发
|
||||
};
|
||||
|
||||
// 根据文件大小调整并发数
|
||||
const fileSizeConfig = {
|
||||
'small': 8, // 小文件(<10MB):8个并发
|
||||
'medium': 6, // 中等文件(10MB-100MB):6个并发
|
||||
'large': 4, // 大文件(>100MB):4个并发
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 错误处理和重试机制
|
||||
|
||||
```javascript
|
||||
class RobustRequestPool extends RequestPool {
|
||||
constructor(maxConcurrent = 6, maxRetries = 3) {
|
||||
super(maxConcurrent);
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
async addRequestWithRetry(task) {
|
||||
return this.addRequest(async () => {
|
||||
let lastError;
|
||||
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
||||
try {
|
||||
return await task();
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.log(`请求失败,第${attempt}次重试:`, error.message);
|
||||
|
||||
if (attempt < this.maxRetries) {
|
||||
// 指数退避
|
||||
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 性能监控和优化
|
||||
|
||||
```javascript
|
||||
class MonitoredRequestPool extends RequestPool {
|
||||
constructor(maxConcurrent = 6) {
|
||||
super(maxConcurrent);
|
||||
this.metrics = {
|
||||
totalRequests: 0,
|
||||
successfulRequests: 0,
|
||||
failedRequests: 0,
|
||||
averageResponseTime: 0,
|
||||
startTime: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
async executeRequest(request) {
|
||||
const startTime = Date.now();
|
||||
this.metrics.totalRequests++;
|
||||
|
||||
try {
|
||||
const result = await super.executeRequest(request);
|
||||
this.metrics.successfulRequests++;
|
||||
this.updateMetrics(Date.now() - startTime);
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.metrics.failedRequests++;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
updateMetrics(responseTime) {
|
||||
const { totalRequests, averageResponseTime } = this.metrics;
|
||||
this.metrics.averageResponseTime =
|
||||
(averageResponseTime * (totalRequests - 1) + responseTime) / totalRequests;
|
||||
}
|
||||
|
||||
getMetrics() {
|
||||
const uptime = Date.now() - this.metrics.startTime;
|
||||
return {
|
||||
...this.metrics,
|
||||
uptime,
|
||||
successRate: (this.metrics.successfulRequests / this.metrics.totalRequests * 100).toFixed(2) + '%',
|
||||
requestsPerSecond: (this.metrics.totalRequests / (uptime / 1000)).toFixed(2)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 内存管理和清理
|
||||
|
||||
```javascript
|
||||
class ManagedRequestPool extends RequestPool {
|
||||
constructor(maxConcurrent = 6, maxMemoryMB = 100) {
|
||||
super(maxConcurrent);
|
||||
this.maxMemory = maxMemoryMB * 1024 * 1024; // 转换为字节
|
||||
this.memoryUsage = 0;
|
||||
}
|
||||
|
||||
async addRequest(task) {
|
||||
// 检查内存使用情况
|
||||
if (this.memoryUsage > this.maxMemory) {
|
||||
console.warn('内存使用过高,等待清理...');
|
||||
await this.waitForMemory();
|
||||
}
|
||||
|
||||
return super.addRequest(task);
|
||||
}
|
||||
|
||||
async waitForMemory() {
|
||||
return new Promise(resolve => {
|
||||
const checkMemory = () => {
|
||||
if (this.memoryUsage < this.maxMemory * 0.8) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkMemory, 1000);
|
||||
}
|
||||
};
|
||||
checkMemory();
|
||||
});
|
||||
}
|
||||
|
||||
// 定期清理
|
||||
startCleanup() {
|
||||
setInterval(() => {
|
||||
this.cleanup();
|
||||
}, 30000); // 每30秒清理一次
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
// 清理过期的请求记录
|
||||
const now = Date.now();
|
||||
// 实现清理逻辑...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 核心算法
|
||||
|
||||
### 1. 分片合并判断
|
||||
|
||||
Reference in New Issue
Block a user