1046 lines
27 KiB
Markdown
1046 lines
27 KiB
Markdown
# 架构优化 - 100并发稳定运行方案 v3.13.0
|
||
|
||
**版本**: v3.13.0
|
||
**日期**: 2025-10-08
|
||
**目标**: 实现100个Token同时并发稳定运行
|
||
|
||
## 目标分析
|
||
|
||
**用户需求**:
|
||
> "我是最终想要100同时并发稳定,该如何进行优化WSS链接呢"
|
||
|
||
**核心挑战**:
|
||
```
|
||
当前瓶颈:
|
||
1. 浏览器WebSocket连接数限制:10-20个
|
||
2. 游戏服务器连接数限制:约20-50个/IP
|
||
3. 网络带宽限制:上行10-20Mbps
|
||
4. 系统资源限制:内存、CPU
|
||
|
||
目标要求:
|
||
✅ 100个Token同时执行
|
||
✅ 稳定性 >95%
|
||
✅ 不降低执行效率
|
||
✅ 浏览器不卡顿
|
||
```
|
||
|
||
## 方案对比
|
||
|
||
| 方案 | 技术难度 | 稳定性 | 效率 | 推荐度 |
|
||
|------|---------|--------|------|--------|
|
||
| **方案A:连接池+轮转** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
| 方案B:分组批次 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||
| 方案C:动态连接管理 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||
| 方案D:混合HTTP/WSS | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
| 方案E:代理服务器 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||
|
||
---
|
||
|
||
## 🏆 方案A:连接池 + 轮转策略(最推荐)
|
||
|
||
### 核心思想
|
||
|
||
```
|
||
不是100个连接同时存在,而是:
|
||
- 维持10-20个活跃WebSocket连接(连接池)
|
||
- 100个任务排队轮流使用这些连接
|
||
- 用完立即释放,给下一个任务使用
|
||
|
||
类比:
|
||
10个电话亭,100个人排队打电话
|
||
每个人打完立即让给下一个人
|
||
```
|
||
|
||
### 架构设计
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ 100 个 Token 任务队列 │
|
||
│ [T1][T2][T3]...[T20][T21]...[T100] │
|
||
└─────────────────────────────────────────────────┘
|
||
↓ 排队获取连接
|
||
┌─────────────────────────────────────────────────┐
|
||
│ WebSocket 连接池(20个连接) │
|
||
│ [WSS1][WSS2]...[WSS20] │
|
||
│ ↑使用中 ↑空闲 ↑使用中 │
|
||
└─────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────┐
|
||
│ 游戏服务器 │
|
||
└─────────────────────────────────────────────────┘
|
||
|
||
流程:
|
||
1. Token1 获取 WSS1,执行任务
|
||
2. Token1 完成,释放 WSS1
|
||
3. Token21 立即获取 WSS1,继续执行
|
||
4. 循环往复,直到100个全部完成
|
||
```
|
||
|
||
### 代码实现
|
||
|
||
#### 1. WebSocket连接池类
|
||
|
||
```javascript
|
||
// src/utils/WebSocketPool.js
|
||
|
||
/**
|
||
* WebSocket连接池管理器
|
||
* 解决100并发问题的核心:复用有限的WebSocket连接
|
||
*/
|
||
export class WebSocketPool {
|
||
constructor(options = {}) {
|
||
this.poolSize = options.poolSize || 20 // 连接池大小
|
||
this.connections = new Map() // 所有连接: tokenId -> connection
|
||
this.availableConnections = [] // 空闲连接队列
|
||
this.waitingQueue = [] // 等待获取连接的任务队列
|
||
this.activeCount = 0 // 当前活跃连接数
|
||
this.reconnectWebSocket = options.reconnectWebSocket // 连接函数
|
||
this.closeConnection = options.closeConnection // 关闭连接函数
|
||
|
||
// 统计信息
|
||
this.stats = {
|
||
totalAcquired: 0,
|
||
totalReleased: 0,
|
||
totalWaiting: 0,
|
||
maxWaitTime: 0,
|
||
avgWaitTime: 0
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取一个可用的WebSocket连接
|
||
* @param {string} tokenId - Token ID
|
||
* @returns {Promise<Object>} WebSocket客户端
|
||
*/
|
||
async acquire(tokenId) {
|
||
const startTime = Date.now()
|
||
|
||
console.log(`🎫 [连接池] Token ${tokenId} 请求连接 (活跃: ${this.activeCount}/${this.poolSize}, 等待: ${this.waitingQueue.length})`)
|
||
|
||
return new Promise(async (resolve, reject) => {
|
||
// 尝试获取连接的函数
|
||
const tryAcquire = async () => {
|
||
// 方式1:复用已有的连接
|
||
if (this.availableConnections.length > 0) {
|
||
const existingTokenId = this.availableConnections.shift()
|
||
const connection = this.connections.get(existingTokenId)
|
||
|
||
if (connection && connection.status === 'connected') {
|
||
// 更新连接的当前使用者
|
||
connection.currentUser = tokenId
|
||
connection.lastUsedTime = Date.now()
|
||
this.activeCount++
|
||
|
||
const waitTime = Date.now() - startTime
|
||
this.updateStats('acquired', waitTime)
|
||
|
||
console.log(`♻️ [连接池] Token ${tokenId} 复用连接 ${existingTokenId} (等待 ${waitTime}ms)`)
|
||
resolve(connection.client)
|
||
return true
|
||
} else {
|
||
// 连接已失效,移除
|
||
this.connections.delete(existingTokenId)
|
||
}
|
||
}
|
||
|
||
// 方式2:创建新连接(如果未达上限)
|
||
if (this.connections.size < this.poolSize) {
|
||
try {
|
||
console.log(`🆕 [连接池] 为 Token ${tokenId} 创建新连接 (${this.connections.size + 1}/${this.poolSize})`)
|
||
|
||
const client = await this.reconnectWebSocket(tokenId)
|
||
|
||
if (!client) {
|
||
throw new Error('连接创建失败')
|
||
}
|
||
|
||
const connection = {
|
||
tokenId: tokenId,
|
||
client: client,
|
||
status: 'connected',
|
||
currentUser: tokenId,
|
||
createdTime: Date.now(),
|
||
lastUsedTime: Date.now()
|
||
}
|
||
|
||
this.connections.set(tokenId, connection)
|
||
this.activeCount++
|
||
|
||
const waitTime = Date.now() - startTime
|
||
this.updateStats('acquired', waitTime)
|
||
|
||
console.log(`✅ [连接池] Token ${tokenId} 创建连接成功 (等待 ${waitTime}ms)`)
|
||
resolve(client)
|
||
return true
|
||
} catch (error) {
|
||
console.error(`❌ [连接池] Token ${tokenId} 创建连接失败:`, error)
|
||
reject(error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// 立即尝试获取
|
||
const acquired = await tryAcquire()
|
||
|
||
if (!acquired) {
|
||
// 需要等待,加入队列
|
||
console.log(`⏳ [连接池] Token ${tokenId} 加入等待队列 (队列长度: ${this.waitingQueue.length + 1})`)
|
||
|
||
this.waitingQueue.push({
|
||
tokenId,
|
||
resolve,
|
||
reject,
|
||
startTime,
|
||
tryAcquire
|
||
})
|
||
|
||
this.stats.totalWaiting++
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 释放连接,供其他任务使用
|
||
* @param {string} tokenId - Token ID
|
||
*/
|
||
release(tokenId) {
|
||
const connection = this.connections.get(tokenId)
|
||
|
||
if (!connection) {
|
||
console.warn(`⚠️ [连接池] Token ${tokenId} 的连接不存在`)
|
||
return
|
||
}
|
||
|
||
console.log(`🔓 [连接池] Token ${tokenId} 释放连接 (活跃: ${this.activeCount - 1}/${this.poolSize}, 等待: ${this.waitingQueue.length})`)
|
||
|
||
this.activeCount--
|
||
connection.currentUser = null
|
||
this.updateStats('released')
|
||
|
||
// 如果有等待的任务,立即分配给它
|
||
if (this.waitingQueue.length > 0) {
|
||
const waiting = this.waitingQueue.shift()
|
||
|
||
console.log(`🔄 [连接池] 连接 ${tokenId} 分配给等待的 Token ${waiting.tokenId}`)
|
||
|
||
// 更新连接的使用者
|
||
connection.currentUser = waiting.tokenId
|
||
connection.lastUsedTime = Date.now()
|
||
this.activeCount++
|
||
|
||
const waitTime = Date.now() - waiting.startTime
|
||
this.updateStats('acquired', waitTime)
|
||
|
||
waiting.resolve(connection.client)
|
||
} else {
|
||
// 没有等待的任务,放入空闲队列
|
||
this.availableConnections.push(tokenId)
|
||
|
||
console.log(`💤 [连接池] 连接 ${tokenId} 进入空闲队列 (空闲: ${this.availableConnections.length})`)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清理所有连接
|
||
*/
|
||
async cleanup() {
|
||
console.log(`🧹 [连接池] 开始清理,共 ${this.connections.size} 个连接`)
|
||
|
||
for (const [tokenId, connection] of this.connections.entries()) {
|
||
try {
|
||
await this.closeConnection(tokenId)
|
||
} catch (error) {
|
||
console.warn(`⚠️ [连接池] 关闭连接 ${tokenId} 失败:`, error)
|
||
}
|
||
}
|
||
|
||
this.connections.clear()
|
||
this.availableConnections = []
|
||
this.activeCount = 0
|
||
|
||
// 拒绝所有等待的任务
|
||
while (this.waitingQueue.length > 0) {
|
||
const waiting = this.waitingQueue.shift()
|
||
waiting.reject(new Error('连接池已清理'))
|
||
}
|
||
|
||
console.log(`✅ [连接池] 清理完成`)
|
||
}
|
||
|
||
/**
|
||
* 更新统计信息
|
||
*/
|
||
updateStats(type, waitTime = 0) {
|
||
if (type === 'acquired') {
|
||
this.stats.totalAcquired++
|
||
if (waitTime > this.stats.maxWaitTime) {
|
||
this.stats.maxWaitTime = waitTime
|
||
}
|
||
this.stats.avgWaitTime =
|
||
(this.stats.avgWaitTime * (this.stats.totalAcquired - 1) + waitTime) /
|
||
this.stats.totalAcquired
|
||
} else if (type === 'released') {
|
||
this.stats.totalReleased++
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取连接池状态
|
||
*/
|
||
getStatus() {
|
||
return {
|
||
poolSize: this.poolSize,
|
||
totalConnections: this.connections.size,
|
||
activeConnections: this.activeCount,
|
||
availableConnections: this.availableConnections.length,
|
||
waitingTasks: this.waitingQueue.length,
|
||
stats: this.stats
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 打印连接池状态
|
||
*/
|
||
printStatus() {
|
||
const status = this.getStatus()
|
||
console.log(`
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
📊 [连接池状态]
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
连接池大小: ${status.poolSize}
|
||
总连接数: ${status.totalConnections}
|
||
活跃连接: ${status.activeConnections}
|
||
空闲连接: ${status.availableConnections}
|
||
等待任务: ${status.waitingTasks}
|
||
|
||
统计信息:
|
||
- 总获取次数: ${status.stats.totalAcquired}
|
||
- 总释放次数: ${status.stats.totalReleased}
|
||
- 总等待次数: ${status.stats.totalWaiting}
|
||
- 最大等待时间: ${status.stats.maxWaitTime.toFixed(0)}ms
|
||
- 平均等待时间: ${status.stats.avgWaitTime.toFixed(0)}ms
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
`)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2. 集成到 batchTaskStore.js
|
||
|
||
```javascript
|
||
// src/stores/batchTaskStore.js
|
||
|
||
import { WebSocketPool } from '@/utils/WebSocketPool'
|
||
|
||
// 在 store 中添加连接池实例
|
||
let wsPool = null
|
||
|
||
/**
|
||
* 初始化连接池
|
||
*/
|
||
const initWebSocketPool = (poolSize = 20) => {
|
||
if (wsPool) {
|
||
// 清理旧的连接池
|
||
wsPool.cleanup()
|
||
}
|
||
|
||
wsPool = new WebSocketPool({
|
||
poolSize,
|
||
reconnectWebSocket: tokenStore.reconnectWebSocket,
|
||
closeConnection: tokenStore.closeWebSocketConnection
|
||
})
|
||
|
||
console.log(`✅ [连接池] 初始化完成,大小: ${poolSize}`)
|
||
|
||
return wsPool
|
||
}
|
||
|
||
/**
|
||
* 使用连接池执行Token任务(优化版)
|
||
*/
|
||
const executeTokenTasksWithPool = async (tokenId, tasks) => {
|
||
const startTime = Date.now()
|
||
let client = null
|
||
|
||
try {
|
||
// 1. 从连接池获取连接
|
||
updateTaskProgress(tokenId, {
|
||
status: 'waiting',
|
||
message: '等待获取连接...'
|
||
})
|
||
|
||
client = await wsPool.acquire(tokenId)
|
||
|
||
updateTaskProgress(tokenId, {
|
||
status: 'executing',
|
||
startTime,
|
||
currentTask: `已获取连接,开始执行任务`
|
||
})
|
||
|
||
// 2. 执行任务(使用获取的连接)
|
||
const results = {}
|
||
let hasError = false
|
||
|
||
for (const task of tasks) {
|
||
if (isPaused.value) {
|
||
await new Promise(resolve => setTimeout(resolve, 500))
|
||
}
|
||
|
||
updateTaskProgress(tokenId, {
|
||
currentTask: `执行: ${task}`
|
||
})
|
||
|
||
try {
|
||
const result = await executeTask(tokenId, task, client)
|
||
results[task] = result
|
||
} catch (error) {
|
||
console.error(`❌ [${tokenId}] 任务 ${task} 失败:`, error)
|
||
results[task] = {
|
||
success: false,
|
||
error: error.message,
|
||
task
|
||
}
|
||
hasError = true
|
||
}
|
||
}
|
||
|
||
// 3. 更新最终状态
|
||
const endTime = Date.now()
|
||
const duration = ((endTime - startTime) / 1000).toFixed(1)
|
||
|
||
updateTaskProgress(tokenId, {
|
||
status: hasError ? 'failed' : 'completed',
|
||
endTime,
|
||
duration: `${duration}s`,
|
||
result: results,
|
||
error: hasError ? '部分任务失败' : null
|
||
})
|
||
|
||
// 4. 更新统计
|
||
if (hasError) {
|
||
executionStats.value.failed++
|
||
} else {
|
||
executionStats.value.success++
|
||
}
|
||
|
||
} catch (error) {
|
||
// 连接获取或执行失败
|
||
const endTime = Date.now()
|
||
const duration = ((endTime - startTime) / 1000).toFixed(1)
|
||
|
||
console.error(`❌ [${tokenId}] 执行失败:`, error)
|
||
|
||
updateTaskProgress(tokenId, {
|
||
status: 'failed',
|
||
endTime,
|
||
duration: `${duration}s`,
|
||
error: error.message || String(error)
|
||
})
|
||
|
||
executionStats.value.failed++
|
||
|
||
} finally {
|
||
// 5. 释放连接(关键!)
|
||
if (client) {
|
||
wsPool.release(tokenId)
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 使用连接池执行批量任务
|
||
*/
|
||
const executeBatchWithPool = async (tokenIds, tasks) => {
|
||
console.log(`
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
🚀 [连接池模式] 开始批量执行
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
Token数量: ${tokenIds.length}
|
||
连接池大小: ${wsPool.poolSize}
|
||
任务列表: ${tasks.join(', ')}
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
`)
|
||
|
||
// 所有任务并发启动(但会在连接池内排队)
|
||
const promises = tokenIds.map(tokenId =>
|
||
executeTokenTasksWithPool(tokenId, tasks)
|
||
)
|
||
|
||
// 每隔5秒打印一次连接池状态
|
||
const statusInterval = setInterval(() => {
|
||
wsPool.printStatus()
|
||
}, 5000)
|
||
|
||
// 等待所有任务完成
|
||
await Promise.all(promises)
|
||
|
||
// 清理定时器
|
||
clearInterval(statusInterval)
|
||
|
||
// 打印最终状态
|
||
wsPool.printStatus()
|
||
}
|
||
|
||
/**
|
||
* 修改 startBatchExecution 使用连接池
|
||
*/
|
||
const startBatchExecution = async (
|
||
tokens = null,
|
||
tasks = null,
|
||
isRetry = false,
|
||
continueFromSaved = false
|
||
) => {
|
||
try {
|
||
// ... 前面的代码保持不变 ...
|
||
|
||
// 🆕 初始化连接池(从localStorage读取配置)
|
||
const poolSize = parseInt(localStorage.getItem('wsPoolSize') || '20')
|
||
initWebSocketPool(poolSize)
|
||
|
||
// 🆕 使用连接池模式执行
|
||
await executeBatchWithPool(tokensToExecute, targetTasks)
|
||
|
||
// 完成执行
|
||
finishBatchExecution()
|
||
|
||
} catch (error) {
|
||
console.error('批量执行失败:', error)
|
||
// ...
|
||
} finally {
|
||
// 🆕 清理连接池
|
||
if (wsPool) {
|
||
await wsPool.cleanup()
|
||
wsPool = null
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 优势分析
|
||
|
||
✅ **突破浏览器限制**:
|
||
```
|
||
传统方式: 100个连接 → 浏览器崩溃
|
||
连接池方式: 20个连接 → 100个任务轮转使用 → 稳定运行
|
||
```
|
||
|
||
✅ **高效复用**:
|
||
```
|
||
连接建立时间: 1-2秒
|
||
任务执行时间: 10-30秒
|
||
|
||
传统方式:
|
||
- 每个任务重新建立连接: 1-2秒浪费
|
||
- 100个任务: 浪费 100-200秒
|
||
|
||
连接池方式:
|
||
- 连接复用,无需重新建立
|
||
- 100个任务: 节省 100-200秒
|
||
```
|
||
|
||
✅ **内存优化**:
|
||
```
|
||
传统方式: 100连接 × 10MB = 1000MB
|
||
连接池方式: 20连接 × 10MB = 200MB
|
||
节省内存: 80%
|
||
```
|
||
|
||
✅ **统计透明**:
|
||
```
|
||
实时监控:
|
||
- 活跃连接数
|
||
- 等待任务数
|
||
- 平均等待时间
|
||
- 最大等待时间
|
||
```
|
||
|
||
---
|
||
|
||
## 🥈 方案B:智能分组批次执行
|
||
|
||
### 核心思想
|
||
|
||
```
|
||
将100个Token分成多个批次:
|
||
- 每批10个,共10批
|
||
- 批内并发执行
|
||
- 批间无缝衔接(前一批完成80%时启动下一批)
|
||
```
|
||
|
||
### 代码实现
|
||
|
||
```javascript
|
||
/**
|
||
* 智能分组批次执行
|
||
* 批次间无缝衔接,提高效率
|
||
*/
|
||
const executeBatchWithSmartGroups = async (tokenIds, tasks) => {
|
||
const BATCH_SIZE = 10 // 每批大小
|
||
const OVERLAP_THRESHOLD = 0.8 // 80%完成时启动下一批
|
||
|
||
const batches = []
|
||
for (let i = 0; i < tokenIds.length; i += BATCH_SIZE) {
|
||
batches.push(tokenIds.slice(i, i + BATCH_SIZE))
|
||
}
|
||
|
||
console.log(`📦 将${tokenIds.length}个Token分为${batches.length}批,每批${BATCH_SIZE}个`)
|
||
|
||
let currentBatchIndex = 0
|
||
let nextBatchStarted = false
|
||
|
||
const executeBatch = async (batch, batchIndex) => {
|
||
console.log(`
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
🔥 执行第 ${batchIndex + 1}/${batches.length} 批
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
Token数量: ${batch.length}
|
||
Token列表: ${batch.join(', ')}
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
`)
|
||
|
||
const promises = batch.map((tokenId, index) =>
|
||
executeTokenTasks(tokenId, tasks).then(() => {
|
||
// 检查是否该启动下一批
|
||
const completed = index + 1
|
||
const progress = completed / batch.length
|
||
|
||
if (
|
||
progress >= OVERLAP_THRESHOLD &&
|
||
batchIndex < batches.length - 1 &&
|
||
!nextBatchStarted
|
||
) {
|
||
nextBatchStarted = true
|
||
console.log(`⚡ 第${batchIndex + 1}批已完成${(progress * 100).toFixed(0)}%,启动下一批`)
|
||
executeBatch(batches[batchIndex + 1], batchIndex + 1)
|
||
}
|
||
})
|
||
)
|
||
|
||
await Promise.all(promises)
|
||
|
||
console.log(`✅ 第 ${batchIndex + 1}/${batches.length} 批执行完成`)
|
||
}
|
||
|
||
// 启动第一批
|
||
await executeBatch(batches[0], 0)
|
||
}
|
||
```
|
||
|
||
### 时间效率对比
|
||
|
||
```
|
||
顺序执行(保守):
|
||
批次1: 0-60秒 (10个Token)
|
||
批次2: 60-120秒
|
||
批次3: 120-180秒
|
||
...
|
||
总时间: 600秒
|
||
|
||
无缝衔接(优化):
|
||
批次1: 0-60秒 (10个Token)
|
||
批次2: 48-108秒 (80%时启动)
|
||
批次3: 96-156秒
|
||
...
|
||
总时间: 约420秒
|
||
节省时间: 30%
|
||
```
|
||
|
||
---
|
||
|
||
## 🥉 方案C:动态连接管理
|
||
|
||
### 核心思想
|
||
|
||
```
|
||
根据任务阶段动态管理连接:
|
||
1. 准备阶段:不建立连接
|
||
2. 执行阶段:建立连接
|
||
3. 完成阶段:立即断开连接
|
||
|
||
目标:将同时活跃连接数控制在20以内
|
||
```
|
||
|
||
### 代码实现
|
||
|
||
```javascript
|
||
/**
|
||
* 动态连接管理器
|
||
*/
|
||
class DynamicConnectionManager {
|
||
constructor(maxConnections = 20) {
|
||
this.maxConnections = maxConnections
|
||
this.activeConnections = new Set()
|
||
this.pendingTasks = []
|
||
}
|
||
|
||
async executeWithConnection(tokenId, taskFn) {
|
||
// 等待直到可以建立连接
|
||
while (this.activeConnections.size >= this.maxConnections) {
|
||
await new Promise(resolve => setTimeout(resolve, 100))
|
||
}
|
||
|
||
// 建立连接
|
||
this.activeConnections.add(tokenId)
|
||
console.log(`🔗 建立连接: ${tokenId} (活跃: ${this.activeConnections.size}/${this.maxConnections})`)
|
||
|
||
try {
|
||
// 执行任务
|
||
const result = await taskFn()
|
||
return result
|
||
} finally {
|
||
// 立即断开连接
|
||
this.activeConnections.delete(tokenId)
|
||
console.log(`🔌 断开连接: ${tokenId} (活跃: ${this.activeConnections.size}/${this.maxConnections})`)
|
||
|
||
// 关闭WebSocket
|
||
await tokenStore.closeWebSocketConnection(tokenId)
|
||
}
|
||
}
|
||
}
|
||
|
||
const connManager = new DynamicConnectionManager(20)
|
||
|
||
const executeTokenTasksDynamic = async (tokenId, tasks) => {
|
||
return connManager.executeWithConnection(tokenId, async () => {
|
||
// 建立连接
|
||
const client = await tokenStore.reconnectWebSocket(tokenId)
|
||
|
||
// 执行所有任务
|
||
const results = {}
|
||
for (const task of tasks) {
|
||
results[task] = await executeTask(tokenId, task, client)
|
||
}
|
||
|
||
return results
|
||
})
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ⭐ 方案D:混合HTTP/WSS模式(终极方案)
|
||
|
||
### 核心思想
|
||
|
||
```
|
||
分析任务特性,分类处理:
|
||
- 实时任务(需要推送):使用WebSocket
|
||
- 非实时任务(请求-响应):使用HTTP API
|
||
|
||
例如:
|
||
WebSocket任务: 爬塔、发车(需要实时状态)
|
||
HTTP任务: 签到、领奖、答题(一次性操作)
|
||
```
|
||
|
||
### 任务分类
|
||
|
||
```javascript
|
||
const TASK_CATEGORIES = {
|
||
// WebSocket任务(需要保持连接)
|
||
realtime: [
|
||
'climbTower', // 爬塔
|
||
'sendCar', // 发车
|
||
'pvpBattle', // PVP对战
|
||
],
|
||
|
||
// HTTP任务(可以用API)
|
||
oneTime: [
|
||
'dailySignIn', // 每日签到
|
||
'legionSignIn', // 俱乐部签到
|
||
'claimReward', // 领取奖励
|
||
'autoStudy', // 一键答题
|
||
'consumeResources', // 消耗资源
|
||
'addClock', // 加钟
|
||
'claimHangupReward', // 领取挂机奖励
|
||
]
|
||
}
|
||
```
|
||
|
||
### HTTP API 实现
|
||
|
||
```javascript
|
||
/**
|
||
* HTTP API调用(替代部分WebSocket)
|
||
*/
|
||
const executeHttpTask = async (tokenId, task) => {
|
||
const token = tokenStore.gameTokens.find(t => t.id === tokenId)
|
||
if (!token) throw new Error('Token不存在')
|
||
|
||
const apiUrl = 'https://xxz-xyzw.hortorgames.com/api'
|
||
|
||
// 根据任务类型构建请求
|
||
const requestConfig = buildHttpRequest(task, token)
|
||
|
||
try {
|
||
const response = await fetch(`${apiUrl}/${requestConfig.endpoint}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token.roleToken}`,
|
||
...requestConfig.headers
|
||
},
|
||
body: JSON.stringify(requestConfig.body)
|
||
})
|
||
|
||
const result = await response.json()
|
||
return result
|
||
|
||
} catch (error) {
|
||
console.error(`HTTP任务失败: ${task}`, error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 混合执行模式
|
||
*/
|
||
const executeTokenTasksHybrid = async (tokenId, tasks) => {
|
||
const httpTasks = tasks.filter(t => TASK_CATEGORIES.oneTime.includes(t))
|
||
const wsTasks = tasks.filter(t => TASK_CATEGORIES.realtime.includes(t))
|
||
|
||
const results = {}
|
||
|
||
// 1. 先执行HTTP任务(不占用WebSocket连接)
|
||
for (const task of httpTasks) {
|
||
try {
|
||
results[task] = await executeHttpTask(tokenId, task)
|
||
console.log(`✅ HTTP任务完成: ${task}`)
|
||
} catch (error) {
|
||
results[task] = { success: false, error: error.message }
|
||
}
|
||
}
|
||
|
||
// 2. 再执行WebSocket任务(从连接池获取)
|
||
if (wsTasks.length > 0) {
|
||
const client = await wsPool.acquire(tokenId)
|
||
|
||
try {
|
||
for (const task of wsTasks) {
|
||
results[task] = await executeTask(tokenId, task, client)
|
||
console.log(`✅ WSS任务完成: ${task}`)
|
||
}
|
||
} finally {
|
||
wsPool.release(tokenId)
|
||
}
|
||
}
|
||
|
||
return results
|
||
}
|
||
```
|
||
|
||
### 优势
|
||
|
||
```
|
||
假设每个Token有10个任务:
|
||
- 7个HTTP任务
|
||
- 3个WebSocket任务
|
||
|
||
传统方式:
|
||
- 需要WebSocket连接: 100%时间
|
||
- 连接池压力: 大
|
||
- 等待时间: 长
|
||
|
||
混合方式:
|
||
- 需要WebSocket连接: 30%时间
|
||
- 连接池压力: 小
|
||
- 等待时间: 短
|
||
- 效率提升: 约2-3倍
|
||
```
|
||
|
||
---
|
||
|
||
## 方案E:本地代理服务器(高级方案)
|
||
|
||
### 核心思想
|
||
|
||
```
|
||
架构:
|
||
浏览器 ←→ 本地Node.js代理 ←→ 游戏服务器
|
||
(1个) (管理100个连接) (接受100个连接)
|
||
|
||
流程:
|
||
1. 浏览器通过HTTP/WebSocket连接本地代理
|
||
2. 代理服务器维护到游戏服务器的100个连接
|
||
3. 浏览器发送指令,代理转发到对应连接
|
||
```
|
||
|
||
### 简化实现
|
||
|
||
```javascript
|
||
// local-proxy-server.js
|
||
const WebSocket = require('ws')
|
||
const express = require('express')
|
||
|
||
const app = express()
|
||
const server = require('http').createServer(app)
|
||
const wss = new WebSocket.Server({ server })
|
||
|
||
// 存储到游戏服务器的连接
|
||
const gameConnections = new Map()
|
||
|
||
// 浏览器连接到代理
|
||
wss.on('connection', (clientWs) => {
|
||
console.log('浏览器已连接')
|
||
|
||
clientWs.on('message', async (message) => {
|
||
const data = JSON.parse(message)
|
||
const { tokenId, action, payload } = data
|
||
|
||
// 获取或创建到游戏服务器的连接
|
||
let gameWs = gameConnections.get(tokenId)
|
||
|
||
if (!gameWs || gameWs.readyState !== WebSocket.OPEN) {
|
||
gameWs = new WebSocket(gameServerUrl)
|
||
gameConnections.set(tokenId, gameWs)
|
||
}
|
||
|
||
// 转发到游戏服务器
|
||
gameWs.send(JSON.stringify(payload))
|
||
|
||
// 监听游戏服务器响应
|
||
gameWs.on('message', (response) => {
|
||
// 转发回浏览器
|
||
clientWs.send(JSON.stringify({
|
||
tokenId,
|
||
response: JSON.parse(response)
|
||
}))
|
||
})
|
||
})
|
||
})
|
||
|
||
server.listen(8080, () => {
|
||
console.log('代理服务器运行在 http://localhost:8080')
|
||
})
|
||
```
|
||
|
||
### 优缺点
|
||
|
||
**优点**:
|
||
- ✅ 完全突破浏览器限制
|
||
- ✅ 可以管理任意数量的连接
|
||
- ✅ 更好的错误处理和重连
|
||
|
||
**缺点**:
|
||
- ❌ 需要安装Node.js
|
||
- ❌ 需要运行额外的服务器
|
||
- ❌ 部署复杂度高
|
||
|
||
---
|
||
|
||
## 推荐实施路线
|
||
|
||
### 阶段1:快速验证(1-2天)
|
||
|
||
**实施方案B:智能分组批次**
|
||
|
||
优势:
|
||
- 代码改动最小
|
||
- 立即可用
|
||
- 稳定性好
|
||
|
||
配置:
|
||
```javascript
|
||
BATCH_SIZE = 10
|
||
OVERLAP_THRESHOLD = 0.8
|
||
总批次 = 10批
|
||
预计时间 = 约7-8分钟(100个Token)
|
||
```
|
||
|
||
### 阶段2:性能优化(3-5天)
|
||
|
||
**实施方案A:连接池 + 轮转**
|
||
|
||
优势:
|
||
- 真正的100并发
|
||
- 资源利用率高
|
||
- 可扩展性强
|
||
|
||
配置:
|
||
```javascript
|
||
POOL_SIZE = 20
|
||
真实并发 = 100个任务
|
||
活跃连接 = 20个
|
||
预计时间 = 约5-6分钟(100个Token)
|
||
```
|
||
|
||
### 阶段3:终极优化(1-2周)
|
||
|
||
**实施方案D:混合HTTP/WSS**
|
||
|
||
优势:
|
||
- 最高效率
|
||
- 最低资源占用
|
||
- 最稳定
|
||
|
||
配置:
|
||
```javascript
|
||
HTTP任务比例 = 70%
|
||
WSS任务比例 = 30%
|
||
连接池大小 = 10-15
|
||
预计时间 = 约3-4分钟(100个Token)
|
||
```
|
||
|
||
---
|
||
|
||
## 性能对比总结
|
||
|
||
| 方案 | 100Token耗时 | 内存占用 | 实施难度 | 稳定性 |
|
||
|------|-------------|---------|---------|--------|
|
||
| 当前方案(20并发) | ~15分钟 | 200MB | - | ⭐⭐⭐⭐ |
|
||
| 方案B(分组批次) | ~8分钟 | 200MB | ⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
| 方案A(连接池) | ~6分钟 | 200MB | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
| 方案D(混合模式) | ~4分钟 | 150MB | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
| 方案E(代理服务器) | ~3分钟 | 500MB | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
|
||
---
|
||
|
||
## 立即行动建议
|
||
|
||
### 建议1:先实施方案A(连接池)
|
||
|
||
**理由**:
|
||
- ✅ 投入产出比最高
|
||
- ✅ 实施难度中等
|
||
- ✅ 效果显著(15分钟 → 6分钟)
|
||
- ✅ 稳定性excellent
|
||
|
||
### 建议2:测试配置
|
||
|
||
```javascript
|
||
// 第一次测试
|
||
{
|
||
mode: '连接池',
|
||
poolSize: 15,
|
||
tokenCount: 50, // 先测50个
|
||
tasks: ['完整套餐']
|
||
}
|
||
|
||
// 如果成功,扩大规模
|
||
{
|
||
poolSize: 20,
|
||
tokenCount: 100,
|
||
tasks: ['完整套餐']
|
||
}
|
||
```
|
||
|
||
### 建议3:监控指标
|
||
|
||
```
|
||
关注以下指标:
|
||
1. 连接成功率 (目标 >98%)
|
||
2. 任务成功率 (目标 >95%)
|
||
3. 平均等待时间 (目标 <5秒)
|
||
4. 总执行时间 (目标 <10分钟)
|
||
5. 浏览器内存 (目标 <500MB)
|
||
```
|
||
|
||
---
|
||
|
||
**状态**: ✅ 方案已提供
|
||
**推荐实施**: 方案A(连接池) + 方案D(混合模式)
|
||
**预期效果**: 100并发稳定运行,总耗时 4-6分钟
|
||
|