281 lines
8.4 KiB
Markdown
281 lines
8.4 KiB
Markdown
|
|
# 🚨 紧急修复:连接池账号混乱问题 v3.13.3
|
|||
|
|
|
|||
|
|
## 问题描述
|
|||
|
|
|
|||
|
|
**症状:**
|
|||
|
|
- 开启连接池模式(100并发 + 连接池大小20)后,单个token任务执行非常慢
|
|||
|
|
- 大量任务超时
|
|||
|
|
- "发车失败 WebSocket未连接" 错误
|
|||
|
|
- "消息处理跳过" 警告
|
|||
|
|
- 感觉指令没有成功发送
|
|||
|
|
|
|||
|
|
**根本原因:**
|
|||
|
|
|
|||
|
|
在 v3.13.0 ~ v3.13.2 版本中,连接池的设计存在**致命缺陷**:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
❌ 错误的设计:
|
|||
|
|
Token A 创建连接 → 执行完任务 → 释放连接
|
|||
|
|
Token B 获取连接 → 复用 Token A 的连接 → 用 Token A 的账号执行 Token B 的任务 ❌
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**问题详解:**
|
|||
|
|
1. 每个 WebSocket 连接在创建时会使用特定 token 的 `roleToken` 进行认证
|
|||
|
|
2. 这个连接会绑定到该 token 对应的游戏账号
|
|||
|
|
3. 当我们把 Token A 的连接复用给 Token B 使用时:
|
|||
|
|
- Token B 发送的指令实际上是以 Token A 的身份发送的
|
|||
|
|
- 服务器拒绝请求(账号不匹配)
|
|||
|
|
- 导致 "WebSocket未连接"、超时、指令无效等错误
|
|||
|
|
|
|||
|
|
## 修复方案
|
|||
|
|
|
|||
|
|
**正确的设计 (v3.13.3):**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
✅ 修复后:
|
|||
|
|
连接池大小 = 20(同时存在的最大连接数)
|
|||
|
|
|
|||
|
|
Token 1-20:立即创建连接,执行任务
|
|||
|
|
Token 21: 等待 Token 1-20 中任何一个完成 → 创建自己的连接 → 执行任务
|
|||
|
|
Token 22: 等待下一个名额 → 创建自己的连接 → 执行任务
|
|||
|
|
...
|
|||
|
|
Token 100: 等待名额 → 创建自己的连接 → 执行任务
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**核心改动:**
|
|||
|
|
1. **每个 token 使用自己的连接**(不复用给其他 token)
|
|||
|
|
2. **连接池只限制同时存在的连接数量**
|
|||
|
|
3. **通过排队机制突破浏览器连接数限制**
|
|||
|
|
|
|||
|
|
## 代码修改
|
|||
|
|
|
|||
|
|
### 1. WebSocketPool.js
|
|||
|
|
|
|||
|
|
#### 修改前 (v3.13.2)
|
|||
|
|
```javascript
|
|||
|
|
async acquire(tokenId) {
|
|||
|
|
// 方式1:复用空闲连接 ❌
|
|||
|
|
if (this.availableConnections.length > 0) {
|
|||
|
|
const existingTokenId = this.availableConnections.shift()
|
|||
|
|
const connection = this.connections.get(existingTokenId)
|
|||
|
|
|
|||
|
|
// 把 existingTokenId 的连接给 tokenId 使用
|
|||
|
|
connection.currentUser = tokenId // ❌ 账号混乱的根源
|
|||
|
|
return connection.client
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 方式2:创建新连接
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
release(tokenId) {
|
|||
|
|
// 把连接放回空闲队列,供其他 token 复用 ❌
|
|||
|
|
this.availableConnections.push(tokenId)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 修改后 (v3.13.3)
|
|||
|
|
```javascript
|
|||
|
|
async acquire(tokenId) {
|
|||
|
|
// 🔹 检查此 token 是否已经有连接
|
|||
|
|
const existing = this.connections.get(tokenId)
|
|||
|
|
if (existing && existing.status === 'connected') {
|
|||
|
|
return existing.client // ✅ 使用自己的连接
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔹 检查是否达到上限
|
|||
|
|
if (this.connections.size >= this.poolSize) {
|
|||
|
|
return null // 需要等待其他 token 释放名额
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔹 创建新连接(专属于此 token)
|
|||
|
|
const client = await this.reconnectWebSocket(tokenId)
|
|||
|
|
this.connections.set(tokenId, { tokenId, client, status: 'connected' })
|
|||
|
|
return client
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async release(tokenId) {
|
|||
|
|
// 🔹 关闭此 token 的连接
|
|||
|
|
await this.closeConnection(tokenId)
|
|||
|
|
this.connections.delete(tokenId)
|
|||
|
|
|
|||
|
|
// 🔹 如果有等待的 token,允许它创建连接
|
|||
|
|
if (this.waitingQueue.length > 0) {
|
|||
|
|
const waiting = this.waitingQueue.shift()
|
|||
|
|
// 让等待的 token 创建自己的连接
|
|||
|
|
waiting.tryAcquire().then(client => waiting.resolve(client))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. batchTaskStore.js
|
|||
|
|
|
|||
|
|
#### 更新日志和注释
|
|||
|
|
```javascript
|
|||
|
|
// 🔹 步骤1:从连接池获取连接
|
|||
|
|
client = await wsPool.acquire(tokenId)
|
|||
|
|
batchLog(`✅ 获取连接成功 (此连接专属于此token)`) // ✅ 明确说明
|
|||
|
|
|
|||
|
|
// 🔹 步骤6:释放连接
|
|||
|
|
await wsPool.release(tokenId) // ✅ 关闭此token的连接,允许等待队列中的token创建连接
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 工作原理对比
|
|||
|
|
|
|||
|
|
### 修改前 (v3.13.2) - 错误设计
|
|||
|
|
```
|
|||
|
|
连接池: [Conn1(TokenA), Conn2(TokenB), ..., Conn20(TokenT)]
|
|||
|
|
↓
|
|||
|
|
TokenU 要执行任务 → 获取 Conn1 → 使用 TokenA 的连接 → ❌ 账号错误
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 修改后 (v3.13.3) - 正确设计
|
|||
|
|
```
|
|||
|
|
连接池名额: [20个空位]
|
|||
|
|
↓
|
|||
|
|
Token 1-20: 占用20个名额,各自创建自己的连接
|
|||
|
|
Token 21-100: 在队列中等待
|
|||
|
|
|
|||
|
|
Token 5 完成 → 释放名额 → Token 21 获得名额 → 创建自己的连接 ✅
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 性能影响
|
|||
|
|
|
|||
|
|
### 优势
|
|||
|
|
✅ **完全避免账号混乱问题**
|
|||
|
|
✅ **突破浏览器连接数限制**(通过排队)
|
|||
|
|
✅ **每个 token 使用正确的账号认证**
|
|||
|
|
✅ **指令能够正确发送和执行**
|
|||
|
|
|
|||
|
|
### 劣势
|
|||
|
|
⚠️ **不能复用连接**(每个 token 需要创建新连接)
|
|||
|
|
⚠️ **创建连接有时间成本**(但避免了错误比速度更重要)
|
|||
|
|
|
|||
|
|
### 性能对比
|
|||
|
|
| 指标 | v3.13.2 (错误设计) | v3.13.3 (正确设计) |
|
|||
|
|
|------|-------------------|-------------------|
|
|||
|
|
| 连接复用 | ✅ 支持(但导致错误) | ❌ 不支持 |
|
|||
|
|
| 账号正确性 | ❌ 会混乱 | ✅ 正确 |
|
|||
|
|
| 任务成功率 | ❌ 低(大量超时) | ✅ 高 |
|
|||
|
|
| 创建连接次数 | 20次 | 100次 |
|
|||
|
|
| 总执行时间 | ❌ 很长(因为错误) | ✅ 正常 |
|
|||
|
|
|
|||
|
|
## 配置建议
|
|||
|
|
|
|||
|
|
### 推荐配置 (100 tokens)
|
|||
|
|
```
|
|||
|
|
✅ 启用连接池模式
|
|||
|
|
✅ 连接池大小: 20
|
|||
|
|
✅ 同时执行数: 5
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**工作流程:**
|
|||
|
|
1. 前 20 个 token 立即创建连接并排队执行(最多5个同时执行任务)
|
|||
|
|
2. 当一个 token 完成所有任务后,释放连接名额
|
|||
|
|
3. 第 21 个 token 获得名额,创建自己的连接
|
|||
|
|
4. 依次类推,直到所有 100 个 token 完成
|
|||
|
|
|
|||
|
|
### 性能调优
|
|||
|
|
```javascript
|
|||
|
|
// 🎯 目标:100并发稳定执行
|
|||
|
|
|
|||
|
|
// 参数1:连接池大小(同时存在的最大连接数)
|
|||
|
|
连接池大小: 20
|
|||
|
|
说明: 浏览器通常限制每个域名 6-10 个连接
|
|||
|
|
20 是一个保守但稳健的值
|
|||
|
|
|
|||
|
|
// 参数2:同时执行数(同时发送请求的 token 数量)
|
|||
|
|
同时执行数: 5
|
|||
|
|
说明: 控制请求频率,避免服务器拥堵
|
|||
|
|
推荐从 5 开始,逐步测试
|
|||
|
|
|
|||
|
|
// 关系:
|
|||
|
|
连接池大小 >= 同时执行数
|
|||
|
|
原因: 正在执行的 token 需要占用连接
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 测试验证
|
|||
|
|
|
|||
|
|
### 测试场景
|
|||
|
|
```
|
|||
|
|
Token 数量: 100
|
|||
|
|
连接池大小: 20
|
|||
|
|
同时执行数: 5
|
|||
|
|
任务: 俱乐部签到 + 发车
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 预期结果
|
|||
|
|
✅ 每个 token 使用自己的连接
|
|||
|
|
✅ 最多 20 个连接同时存在
|
|||
|
|
✅ 最多 5 个 token 同时执行任务
|
|||
|
|
✅ 任务指令正确发送
|
|||
|
|
✅ 无 "WebSocket未连接" 错误
|
|||
|
|
✅ 无账号混乱问题
|
|||
|
|
|
|||
|
|
### 日志示例
|
|||
|
|
```
|
|||
|
|
🎫 [Token001] 请求连接...
|
|||
|
|
✅ [Token001] 获取连接成功 (此连接专属于此token)
|
|||
|
|
📌 [Token001] 执行任务: 俱乐部签到
|
|||
|
|
✅ [Token001] 任务完成
|
|||
|
|
🔓 [Token001] 释放连接
|
|||
|
|
🔄 [连接池v3.13.3] 释放名额,允许 Token021 创建连接
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 版本历史
|
|||
|
|
|
|||
|
|
### v3.13.0 (2025-10-08)
|
|||
|
|
- ✅ 首次引入连接池概念
|
|||
|
|
- ❌ 设计缺陷:复用连接导致账号混乱
|
|||
|
|
|
|||
|
|
### v3.13.1
|
|||
|
|
- ✅ 优化连接池性能
|
|||
|
|
- ❌ 仍存在账号混乱问题
|
|||
|
|
|
|||
|
|
### v3.13.2
|
|||
|
|
- ✅ 引入请求节流
|
|||
|
|
- ❌ 仍存在账号混乱问题
|
|||
|
|
|
|||
|
|
### v3.13.3 (本次修复)
|
|||
|
|
- ✅ **修复账号混乱问题**
|
|||
|
|
- ✅ 每个 token 使用自己的连接
|
|||
|
|
- ✅ 连接池只限制数量,不复用连接
|
|||
|
|
- ✅ 通过排队机制突破浏览器限制
|
|||
|
|
|
|||
|
|
## 常见问题
|
|||
|
|
|
|||
|
|
### Q: 为什么不能复用连接?
|
|||
|
|
A: 因为每个 WebSocket 连接在建立时会用特定 token 的 `roleToken` 认证,绑定到特定游戏账号。复用会导致用错误的账号发送指令。
|
|||
|
|
|
|||
|
|
### Q: 这会不会导致创建很多连接?
|
|||
|
|
A: 是的,100 个 token 会创建 100 个连接,但**不是同时创建**。通过连接池限制,同时最多存在 20 个连接,其他的排队等待。
|
|||
|
|
|
|||
|
|
### Q: 比 v3.13.2 慢吗?
|
|||
|
|
A: 创建连接有时间成本,但 v3.13.2 因为账号混乱导致大量任务失败和超时,实际上更慢。v3.13.3 虽然多了创建连接的时间,但任务成功率高,总体更快。
|
|||
|
|
|
|||
|
|
### Q: 连接池大小应该设置多少?
|
|||
|
|
A: 推荐 15-20。不要设置太大(避免浏览器限制),也不要太小(会导致等待时间过长)。
|
|||
|
|
|
|||
|
|
### Q: 同时执行数应该设置多少?
|
|||
|
|
A: 推荐 5。这是"同时发送请求的 token 数量",太大会拥堵服务器,太小会降低效率。
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
v3.13.3 是一个**关键修复版本**,解决了 v3.13.0-v3.13.2 中连接池设计的根本缺陷。
|
|||
|
|
|
|||
|
|
**核心改变:**
|
|||
|
|
- ❌ 不再复用连接给其他 token
|
|||
|
|
- ✅ 每个 token 使用自己的连接
|
|||
|
|
- ✅ 连接池只限制同时存在的连接数量
|
|||
|
|
- ✅ 通过排队机制实现高并发
|
|||
|
|
|
|||
|
|
**用户影响:**
|
|||
|
|
- ✅ 100并发任务能够正常执行
|
|||
|
|
- ✅ 无账号混乱问题
|
|||
|
|
- ✅ 指令正确发送
|
|||
|
|
- ✅ 任务成功率显著提升
|
|||
|
|
|
|||
|
|
**升级建议:**
|
|||
|
|
🚨 **强烈建议所有使用连接池模式的用户立即升级到 v3.13.3**
|
|||
|
|
|