Files
NeoBot/MULTI_FRONTEND_SUPPORT.md
K2cr2O1 0baf07a716 feat(反向WS管理): 实现多前端支持与完善清理机制
- 为每个前端创建独立的Bot实例,防止状态混乱
- 分离消息锁和时间戳存储,修复清理逻辑错误
- 完善客户端断开时的清理逻辑,包括负载计数和健康状态
- 添加文档说明多前端支持的功能和解决方案
2026-02-28 21:20:20 +08:00

5.3 KiB
Raw Blame History

多前端支持问题分析和解决方案

已实现的功能

1. 负载均衡

  • 自动选择负载最低的健康客户端
  • 健康检查30秒内有活动
  • 负载计数(消息处理次数)
  • API调用时自动切换客户端

2. 防重复发送

  • 事件ID检查id/post_id/time
  • 消息锁机制(异步锁)
  • 双重检查(锁内外各一次)
  • 自动清理过期数据

3. 多前端支持

  • 每个前端独立的Bot实例
  • 客户端连接/断开管理
  • 完整的清理机制

已修复的问题

1. 清理不完整

问题:客户端断开连接时没有清理负载计数和健康状态

解决方案

async def _disconnect_client(self, client_id: str) -> None:
    if client_id in self.clients:
        del self.clients[client_id]
    if client_id in self.client_self_ids:
        del self.client_self_ids[client_id]
    if client_id in self._client_load:
        del self._client_load[client_id]
    if client_id in self._client_health:
        del self._client_health[client_id]
    if client_id in self.bots:
        del self.bots[client_id]

2. Bot实例共享

问题多个前端共享同一个Bot实例可能导致状态混乱

解决方案为每个前端创建独立的Bot实例

# Bot实例字典每个前端独立的Bot实例
self.bots: Dict[str, Bot] = {}

# 为每个前端创建独立的Bot实例
if client_id not in self.bots:
    temp_ws = WS()
    temp_ws.self_id = event.self_id if hasattr(event, 'self_id') else 0
    self.bots[client_id] = Bot(temp_ws)

event.bot = self.bots[client_id]

3. 清理逻辑错误

问题清理过期数据时将Lock对象当作时间戳来处理

解决方案分离存储Lock对象和时间戳

# 分离存储
self._message_locks: Dict[str, asyncio.Lock] = {}  # 存储Lock对象
self._message_lock_times: Dict[str, datetime] = {}  # 存储Lock的创建时间

# 清理时使用时间戳字典
expired_locks = [
    lock_key for lock_key, timestamp in self._message_lock_times.items()
    if (current_time - timestamp).total_seconds() > self._lock_ttl
]

可能存在的问题

1. API响应混淆

问题描述:当多个前端同时响应时,_pending_requests是全局共享的,可能导致响应匹配错误

当前解决方案使用echo_id匹配响应任何前端的响应都会被正确匹配

echo_id = data.get("echo")
if echo_id and echo_id in self._pending_requests:
    future = self._pending_requests.pop(echo_id)
    if not future.done():
        future.set_result(data)

潜在风险如果多个前端同时响应同一个API请求只有第一个响应会被处理

建议优化可以考虑在API调用时指定客户端ID确保响应来自正确的客户端

2. 负载均衡不准确

问题描述:负载计数可能不准确,因为多个前端可能同时处理相同的消息

当前解决方案:负载计数只是参考,系统会优先选择健康的客户端

建议优化:可以考虑使用更复杂的负载均衡策略,如加权轮询

3. Bot实例状态

问题描述每个前端有独立的Bot实例可能导致状态不一致

当前解决方案Bot实例是无状态的只依赖于WS实例和self_id

建议优化如果需要共享状态可以使用Redis等外部存储

最佳实践

1. 部署建议

  • 部署2-3个前端实例进行负载均衡
  • 确保前端实例之间的网络连接稳定
  • 定期检查前端连接状态

2. 监控建议

  • 关注重复事件日志,排查网络问题
  • 监控客户端健康状态
  • 监控API调用成功率

3. 调试建议

  • 使用get_healthy_clients()查看健康客户端
  • 使用get_client_with_least_load()查看负载最低的客户端
  • 查看日志了解API调用情况

性能优化建议

1. API响应过滤

可以在API调用时记录客户端ID确保响应来自正确的客户端

# 记录API请求的客户端ID
self._api_requests[echo_id] = {
    'client_id': client_id,
    'timestamp': datetime.now()
}

# 处理响应时验证客户端ID
if echo_id in self._api_requests:
    request_info = self._api_requests[echo_id]
    if request_info['client_id'] == client_id:
        # 处理响应

2. 负载均衡策略

可以考虑使用更复杂的负载均衡策略:

  • 加权轮询:根据客户端性能分配权重
  • 最少连接:选择连接数最少的客户端
  • 响应时间:选择响应时间最短的客户端

3. 缓存优化

可以考虑使用Redis缓存Bot实例的状态

# 缓存Bot实例的状态
await redis_manager.set(f"neobot:bot:{client_id}:status", status_data, ex=300)

故障排查

问题:消息重复处理

原因:网络延迟导致前端重复发送

解决系统已自动处理检查事件ID是否正确设置

问题API调用超时

原因:选择的客户端不健康或网络问题

解决:系统会自动切换到其他健康客户端

问题:所有客户端都不健康

原因:前端断开连接或网络问题

解决:检查前端连接状态和网络连接

问题Bot实例初始化失败

原因WS实例缺失或self_id未设置

解决确保在事件处理时正确设置self_id