feat: 添加多线程架构支持并优化性能

实现线程管理器以支持高并发场景,添加GIL-free模式提升Python 3.14下的多线程性能
新增B站API集成和本地文件服务器功能,改进镜像插件支持GIF处理
更新文档说明多线程架构和GIL-free模式的使用方法
This commit is contained in:
2026-03-01 16:01:51 +08:00
parent 734c112ee4
commit ff4a4d92a5
20 changed files with 2071 additions and 317 deletions

View File

@@ -11,15 +11,12 @@ from websockets.server import WebSocketServerProtocol
from typing import Dict, Any, Optional, Set
from datetime import datetime
import uuid
import random
import threading
from ..config_loader import global_config
from ..utils.logger import ModuleLogger
from ..utils.exceptions import WebSocketError, WebSocketConnectionError
from ..utils.error_codes import ErrorCode, create_error_response
from .command_manager import matcher
from models.events.factory import EventFactory
from .redis_manager import redis_manager
from ..bot import Bot
from ..ws import ReverseWSClient as _ReverseWSClient
@@ -82,6 +79,18 @@ class ReverseWSManager:
# 正在处理的事件ID集合用于防止重复处理
self._processing_events: Dict[str, Set[str]] = {} # client_id: set of event_ids
# 线程安全锁
self._clients_lock = threading.RLock()
self._bots_lock = threading.RLock()
self._pending_requests_lock = threading.RLock()
self._load_lock = threading.RLock()
self._health_lock = threading.RLock()
self._processed_events_lock = threading.RLock()
self._processed_messages_lock = threading.RLock()
self._processing_events_lock = threading.RLock()
self._message_locks_lock = threading.RLock()
self._message_lock_times_lock = threading.RLock()
async def start(self, host: str = "0.0.0.0", port: int = 3002) -> None:
"""
启动反向 WebSocket 服务端。
@@ -184,37 +193,41 @@ class ReverseWSManager:
current_time = datetime.now()
# 清理过期的事件ID按客户端
for client_id, events in list(self._processed_events.items()):
expired_events = [
event_id for event_id, timestamp in events.items()
if (current_time - timestamp).total_seconds() > self._event_ttl
]
for event_id in expired_events:
del events[event_id]
if not events:
del self._processed_events[client_id]
with self._processed_events_lock:
for client_id, events in list(self._processed_events.items()):
expired_events = [
event_id for event_id, timestamp in events.items()
if (current_time - timestamp).total_seconds() > self._event_ttl
]
for event_id in expired_events:
del events[event_id]
if not events:
del self._processed_events[client_id]
# 清理过期的消息锁
expired_locks = [
lock_key for lock_key, timestamp in self._message_lock_times.items()
if (current_time - timestamp).total_seconds() > self._lock_ttl
]
for lock_key in expired_locks:
if lock_key in self._message_locks:
del self._message_locks[lock_key]
if lock_key in self._message_lock_times:
del self._message_lock_times[lock_key]
with self._message_lock_times_lock:
expired_locks = [
lock_key for lock_key, timestamp in self._message_lock_times.items()
if (current_time - timestamp).total_seconds() > self._lock_ttl
]
for lock_key in expired_locks:
with self._message_locks_lock:
if lock_key in self._message_locks:
del self._message_locks[lock_key]
if lock_key in self._message_lock_times:
del self._message_lock_times[lock_key]
# 清理过期的消息内容(按客户端)
for client_id, messages in list(self._processed_messages.items()):
expired_messages = [
msg_key for msg_key, timestamp in messages.items()
if (current_time - timestamp).total_seconds() > self._message_content_ttl
]
for msg_key in expired_messages:
del messages[msg_key]
if not messages:
del self._processed_messages[client_id]
with self._processed_messages_lock:
for client_id, messages in list(self._processed_messages.items()):
expired_messages = [
msg_key for msg_key, timestamp in messages.items()
if (current_time - timestamp).total_seconds() > self._message_content_ttl
]
for msg_key in expired_messages:
del messages[msg_key]
if not messages:
del self._processed_messages[client_id]
except asyncio.CancelledError:
break
@@ -228,24 +241,32 @@ class ReverseWSManager:
Args:
client_id: 客户端 ID
"""
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]
with self._clients_lock:
if client_id in self.clients:
del self.clients[client_id]
with self._clients_lock:
if client_id in self.client_self_ids:
del self.client_self_ids[client_id]
with self._load_lock:
if client_id in self._client_load:
del self._client_load[client_id]
with self._health_lock:
if client_id in self._client_health:
del self._client_health[client_id]
with self._bots_lock:
if client_id in self.bots:
del self.bots[client_id]
# 清理该客户端的防重复数据
if client_id in self._processed_events:
del self._processed_events[client_id]
if client_id in self._processed_messages:
del self._processed_messages[client_id]
if client_id in self._processing_events:
del self._processing_events[client_id]
with self._processed_events_lock:
if client_id in self._processed_events:
del self._processed_events[client_id]
with self._processed_messages_lock:
if client_id in self._processed_messages:
del self._processed_messages[client_id]
with self._processing_events_lock:
if client_id in self._processing_events:
del self._processing_events[client_id]
self.logger.info(f"客户端已断开并清理: {client_id}")
@@ -266,41 +287,46 @@ class ReverseWSManager:
event_key = f"{event_data.get('post_type')}:{event_id}"
# 检查客户端是否已连接
if client_id not in self.clients:
self.logger.debug(f"_on_event: 客户端已断开, client_id={client_id}")
return
with self._clients_lock:
if client_id not in self.clients:
self.logger.debug(f"_on_event: 客户端已断开, client_id={client_id}")
return
# 检查是否正在处理
if client_id not in self._processing_events:
self._processing_events[client_id] = set()
with self._processing_events_lock:
if client_id not in self._processing_events:
self._processing_events[client_id] = set()
if event_key in self._processing_events[client_id]:
self.logger.debug(f"_on_event: 事件正在处理中, client_id={client_id}, event_key={event_key}")
return
if event_key in self._processing_events[client_id]:
self.logger.debug(f"_on_event: 事件正在处理中, client_id={client_id}, event_key={event_key}")
return
# 标记为正在处理
self._processing_events[client_id].add(event_key)
# 标记为正在处理
self._processing_events[client_id].add(event_key)
try:
event = EventFactory.create_event(event_data)
if hasattr(event, 'self_id'):
self.client_self_ids[client_id] = event.self_id
with self._clients_lock:
self.client_self_ids[client_id] = event.self_id
# 为事件注入Bot实例
from ..ws import ReverseWSClient
# 为每个前端创建独立的Bot实例
if client_id not in self.bots:
# 使用 ReverseWSClient 代理
temp_ws = ReverseWSClient(self, client_id)
temp_ws.self_id = event.self_id if hasattr(event, 'self_id') else 0
self.bots[client_id] = Bot(temp_ws)
with self._bots_lock:
if client_id not in self.bots:
# 使用 ReverseWSClient 代理
temp_ws = ReverseWSClient(self, client_id)
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]
# 记录客户端健康状态
self._client_health[client_id] = datetime.now()
with self._health_lock:
self._client_health[client_id] = datetime.now()
# 检查是否为重复事件(按客户端)
is_duplicate = self._is_duplicate_event(event_data, client_id)
@@ -333,15 +359,18 @@ class ReverseWSManager:
return
# 标记事件已处理(按客户端)
self._mark_event_processed(event_data, client_id)
with self._processed_events_lock:
self._mark_event_processed(event_data, client_id)
# 更新客户端负载
self._update_client_load(client_id)
with self._load_lock:
self._update_client_load(client_id)
await matcher.handle_event(event.bot, event)
else:
# 对于非消息事件,直接标记并处理
self._mark_event_processed(event_data, client_id)
with self._processed_events_lock:
self._mark_event_processed(event_data, client_id)
if event.post_type == "notice":
notice_type = getattr(event, "notice_type", "Unknown")
@@ -362,12 +391,13 @@ class ReverseWSManager:
self.logger.exception(f"事件处理异常: {str(e)}")
finally:
# 清理正在处理的事件
if client_id in self._processing_events:
if event_key in self._processing_events[client_id]:
self._processing_events[client_id].discard(event_key)
# 如果集合为空,删除该客户端的记录
if not self._processing_events[client_id]:
del self._processing_events[client_id]
with self._processing_events_lock:
if client_id in self._processing_events:
if event_key in self._processing_events[client_id]:
self._processing_events[client_id].discard(event_key)
# 如果集合为空,删除该客户端的记录
if not self._processing_events[client_id]:
del self._processing_events[client_id]
async def call_api(
self,
@@ -404,28 +434,39 @@ class ReverseWSManager:
# 选择负载最低的客户端
client_id = self.get_client_with_least_load()
if client_id is None and healthy_clients:
client_id = list(healthy_clients.keys())[0]
with self._clients_lock:
client_id = list(healthy_clients.keys())[0]
else:
# 如果没有健康客户端,使用所有客户端中的一个
client_id = list(self.clients.keys())[0]
with self._clients_lock:
client_id = list(self.clients.keys())[0]
echo_id = str(uuid.uuid4())
payload = {"action": action, "params": params or {}, "echo": echo_id}
loop = asyncio.get_running_loop()
future = loop.create_future()
self._pending_requests[echo_id] = future
with self._pending_requests_lock:
self._pending_requests[echo_id] = future
try:
targets = [client_id] if client_id else list(self.clients.keys())
targets = [client_id] if client_id else None
clients_to_send = []
for cid in targets:
if cid in self.clients:
await self.clients[cid].send(orjson.dumps(payload))
with self._clients_lock:
if targets is None:
targets = list(self.clients.keys())
for cid in targets:
if cid in self.clients:
clients_to_send.append((cid, self.clients[cid]))
for cid, websocket in clients_to_send:
await websocket.send(orjson.dumps(payload))
return await asyncio.wait_for(future, timeout=30.0)
except asyncio.TimeoutError:
self._pending_requests.pop(echo_id, None)
with self._pending_requests_lock:
self._pending_requests.pop(echo_id, None)
self.logger.warning(f"API 调用超时: action={action}, params={params}")
return create_error_response(
code=ErrorCode.TIMEOUT_ERROR,
@@ -433,7 +474,8 @@ class ReverseWSManager:
data={"action": action, "params": params}
)
except Exception as e:
self._pending_requests.pop(echo_id, None)
with self._pending_requests_lock:
self._pending_requests.pop(echo_id, None)
self.logger.exception(f"API 调用异常: action={action}, error={str(e)}")
return create_error_response(
code=ErrorCode.WS_MESSAGE_ERROR,
@@ -448,7 +490,8 @@ class ReverseWSManager:
Returns:
客户端 ID 和 self_id 的映射字典
"""
return self.client_self_ids.copy()
with self._clients_lock:
return self.client_self_ids.copy()
def _is_duplicate_event(self, event_data: Dict[str, Any], client_id: str) -> bool:
"""
@@ -472,13 +515,14 @@ class ReverseWSManager:
event_key = f"{event_data.get('post_type')}:{event_id}"
# 检查该客户端是否已处理过此事件
if client_id not in self._processed_events:
self.logger.debug(f"_is_duplicate_event: client_id={client_id}不在_processed_events中, event_key={event_key}, 返回False")
return False
with self._processed_events_lock:
if client_id not in self._processed_events:
self.logger.debug(f"_is_duplicate_event: client_id={client_id}不在_processed_events中, event_key={event_key}, 返回False")
return False
is_duplicate = event_key in self._processed_events[client_id]
self.logger.debug(f"_is_duplicate_event: client_id={client_id}, event_key={event_key}, in_processed={is_duplicate}, processed_events_count={len(self._processed_events[client_id])}")
return is_duplicate
is_duplicate = event_key in self._processed_events[client_id]
self.logger.debug(f"_is_duplicate_event: client_id={client_id}, event_key={event_key}, in_processed={is_duplicate}, processed_events_count={len(self._processed_events[client_id])}")
return is_duplicate
def _is_duplicate_message(self, event_data: Dict[str, Any], client_id: str) -> bool:
"""
@@ -507,10 +551,11 @@ class ReverseWSManager:
content_key = f"content:{raw_message}:{user_id}:{group_id}"
# 检查该客户端是否已处理过此消息内容
if client_id not in self._processed_messages:
return False
with self._processed_messages_lock:
if client_id not in self._processed_messages:
return False
return content_key in self._processed_messages[client_id]
return content_key in self._processed_messages[client_id]
def _mark_event_processed(self, event_data: Dict[str, Any], client_id: str) -> None:
"""
@@ -532,10 +577,11 @@ class ReverseWSManager:
event_key = f"{event_data.get('post_type')}:{event_id}"
# 为该客户端记录已处理的事件
if client_id not in self._processed_events:
self._processed_events[client_id] = {}
self._processed_events[client_id][event_key] = datetime.now()
self.logger.debug(f"_mark_event_processed: client_id={client_id}, event_key={event_key}, processed_events_count={len(self._processed_events[client_id])}")
with self._processed_events_lock:
if client_id not in self._processed_events:
self._processed_events[client_id] = {}
self._processed_events[client_id][event_key] = datetime.now()
self.logger.debug(f"_mark_event_processed: client_id={client_id}, event_key={event_key}, processed_events_count={len(self._processed_events[client_id])}")
# 只对群聊消息标记内容已处理
if event_data.get('post_type') == 'message' and event_data.get('message_type') == 'group':
@@ -544,9 +590,10 @@ class ReverseWSManager:
group_id = event_data.get('group_id', '0')
content_key = f"content:{raw_message}:{user_id}:{group_id}"
if client_id not in self._processed_messages:
self._processed_messages[client_id] = {}
self._processed_messages[client_id][content_key] = datetime.now()
with self._processed_messages_lock:
if client_id not in self._processed_messages:
self._processed_messages[client_id] = {}
self._processed_messages[client_id][content_key] = datetime.now()
def _get_message_key(self, event_data: Dict[str, Any]) -> str:
"""
@@ -574,9 +621,11 @@ class ReverseWSManager:
Returns:
asyncio.Lock 实例
"""
if key not in self._message_locks:
self._message_locks[key] = asyncio.Lock()
self._message_lock_times[key] = datetime.now()
with self._message_locks_lock:
if key not in self._message_locks:
self._message_locks[key] = asyncio.Lock()
with self._message_lock_times_lock:
self._message_lock_times[key] = datetime.now()
return self._message_locks[key]
def _update_client_load(self, client_id: str) -> None:
@@ -586,9 +635,10 @@ class ReverseWSManager:
Args:
client_id: 客户端 ID
"""
if client_id not in self._client_load:
self._client_load[client_id] = 0
self._client_load[client_id] += 1
with self._load_lock:
if client_id not in self._client_load:
self._client_load[client_id] = 0
self._client_load[client_id] += 1
def get_client_with_least_load(self) -> Optional[str]:
"""
@@ -597,10 +647,11 @@ class ReverseWSManager:
Returns:
客户端 ID如果没有客户端则返回 None
"""
if not self._client_load:
return None
with self._load_lock:
if not self._client_load:
return None
return min(self._client_load.keys(), key=lambda k: self._client_load[k])
return min(self._client_load.keys(), key=lambda k: self._client_load[k])
def get_healthy_clients(self) -> Dict[str, int]:
"""
@@ -612,11 +663,13 @@ class ReverseWSManager:
current_time = datetime.now()
healthy = {}
for client_id, last_health in self._client_health.items():
if (current_time - last_health).total_seconds() < 30:
if client_id in self.client_self_ids:
healthy[client_id] = self.client_self_ids[client_id]
with self._health_lock:
with self._clients_lock:
for client_id, last_health in self._client_health.items():
if (current_time - last_health).total_seconds() < 30:
if client_id in self.client_self_ids:
healthy[client_id] = self.client_self_ids[client_id]
return healthy