feat: add retry mechanism and cache fallback for auth timeout

This commit is contained in:
HibiKier
2025-12-17 10:52:15 +08:00
parent c9f0a8b9d9
commit 0939013a89
+151 -30
View File
@@ -11,9 +11,10 @@ from tortoise.exceptions import IntegrityError
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.user_console import UserConsole
from zhenxun.services.cache import CacheRoot
from zhenxun.services.data_access import DataAccess
from zhenxun.services.log import logger
from zhenxun.utils.enum import GoldHandle, PluginType
from zhenxun.utils.enum import CacheType, GoldHandle, PluginType
from zhenxun.utils.exception import InsufficientGold
from zhenxun.utils.platform import PlatformUtils
from zhenxun.utils.utils import get_entity_ids
@@ -36,6 +37,10 @@ from .auth.utils import base_config
# 超时设置(秒)
TIMEOUT_SECONDS = 5.0
# 快速重试超时(秒)- 用于降级策略
FAST_RETRY_TIMEOUT = 2.0
# 最大重试次数
MAX_RETRY_COUNT = 2
# 熔断计数器
CIRCUIT_BREAKERS = {
"auth_ban": {"failures": 0, "threshold": 3, "active": False, "reset_time": 0},
@@ -121,13 +126,14 @@ def check_circuit_breaker(name):
async def get_plugin_and_user(
module: str, user_id: str
module: str, user_id: str, use_cache_fallback: bool = True
) -> tuple[PluginInfo, UserConsole]:
"""获取用户数据和插件信息
参数:
module: 模块名
user_id: 用户id
use_cache_fallback: 是否在超时时使用缓存降级策略
异常:
PermissionExemption: 插件数据不存在
@@ -154,12 +160,51 @@ async def get_plugin_and_user(
except asyncio.TimeoutError:
# 如果并行查询超时,尝试串行查询
logger.warning("并行查询超时,尝试串行查询", LOGGER_COMMAND)
plugin = await with_timeout(
plugin_dao.safe_get_or_none(module=module), name="get_plugin"
)
user = await with_timeout(
user_dao.safe_get_or_none(user_id=user_id), name="get_user"
)
try:
plugin = await with_timeout(
plugin_dao.safe_get_or_none(module=module), name="get_plugin"
)
user = await with_timeout(
user_dao.safe_get_or_none(user_id=user_id), name="get_user"
)
except asyncio.TimeoutError:
# 如果串行查询也超时,尝试从缓存获取(降级策略)
if use_cache_fallback:
logger.warning(
f"数据库查询超时,尝试从缓存获取数据,模块: {module}, "
f"用户: {user_id}",
LOGGER_COMMAND,
)
try:
# 尝试从缓存获取插件信息
plugin = await CacheRoot.get(CacheType.PLUGINS, {"module": module})
# 尝试从缓存获取用户信息
user = await CacheRoot.get(CacheType.USERS, {"user_id": user_id})
if plugin and user:
logger.info(
f"成功从缓存获取数据,模块: {module}, 用户: {user_id}",
LOGGER_COMMAND,
)
elif not plugin:
raise PermissionExemption(
f"插件:{module} 数据不存在(缓存中也没有),"
f"已跳过权限检查..."
)
elif not user:
raise PermissionExemption(
"用户数据不存在(缓存中也没有),已跳过权限检查..."
)
except Exception as e:
logger.error(
f"从缓存获取数据失败: {e}",
LOGGER_COMMAND,
)
raise PermissionExemption(
"获取插件和用户数据超时且缓存获取失败,请稍后再试..."
)
else:
raise PermissionExemption("获取插件和用户数据超时,请稍后再试...")
except IntegrityError:
await asyncio.sleep(0.5)
plugin_task = plugin_dao.safe_get_or_none(module=module)
@@ -176,13 +221,14 @@ async def get_plugin_and_user(
raise PermissionExemption(
f"插件: {plugin.name}:{plugin.module} 为HIDDEN,已跳过权限检查..."
)
user = None
try:
user = await user_dao.get_by_func_or_none(
UserConsole.get_user, False, user_id=user_id
)
except IntegrityError as e:
raise PermissionExemption("重复创建用户,已跳过该次权限检查...") from e
# 如果user已经获取到了,就不需要再次查询
if user is None:
try:
user = await user_dao.get_by_func_or_none(
UserConsole.get_user, False, user_id=user_id
)
except IntegrityError as e:
raise PermissionExemption("重复创建用户,已跳过该次权限检查...") from e
if not user:
raise PermissionExemption("用户数据不存在,已跳过权限检查...")
return plugin, user
@@ -328,20 +374,93 @@ async def auth(
if not module:
raise PermissionExemption("Matcher插件名称不存在...")
# 获取插件和用户数据
# 获取插件和用户数据(带重试和降级策略)
plugin_user_start = time.time()
try:
plugin, user = await with_timeout(
get_plugin_and_user(module, entity.user_id), name="get_plugin_and_user"
)
hook_times["get_plugin_user"] = f"{time.time() - plugin_user_start:.3f}s"
except asyncio.TimeoutError:
logger.error(
f"获取插件和用户数据超时,模块: {module}",
LOGGER_COMMAND,
session=session,
)
raise PermissionExemption("获取插件和用户数据超时,请稍后再试...")
plugin = None
user = None
retry_count = 0
while retry_count <= MAX_RETRY_COUNT:
try:
plugin, user = await with_timeout(
get_plugin_and_user(
module, entity.user_id, use_cache_fallback=(retry_count > 0)
),
timeout=FAST_RETRY_TIMEOUT if retry_count > 0 else TIMEOUT_SECONDS,
name="get_plugin_and_user",
)
hook_times["get_plugin_user"] = (
f"{time.time() - plugin_user_start:.3f}s"
)
if retry_count > 0:
logger.info(
f"重试成功获取插件和用户数据,模块: {module}, "
f"重试次数: {retry_count}",
LOGGER_COMMAND,
session=session,
)
break
except asyncio.TimeoutError:
retry_count += 1
if retry_count <= MAX_RETRY_COUNT:
logger.warning(
f"获取插件和用户数据超时,尝试重试 "
f"({retry_count}/{MAX_RETRY_COUNT}),模块: {module}",
LOGGER_COMMAND,
session=session,
)
# 短暂等待后重试
await asyncio.sleep(0.1 * retry_count)
else:
logger.error(
f"获取插件和用户数据超时,已重试 {MAX_RETRY_COUNT} 次,"
f"模块: {module}",
LOGGER_COMMAND,
session=session,
)
# 最后一次尝试:使用缓存降级策略
try:
plugin, user = await get_plugin_and_user(
module, entity.user_id, use_cache_fallback=True
)
hook_times["get_plugin_user"] = (
f"{time.time() - plugin_user_start:.3f}s (缓存降级)"
)
logger.info(
f"使用缓存降级策略成功获取数据,模块: {module}",
LOGGER_COMMAND,
session=session,
)
break
except Exception as e:
logger.error(
f"缓存降级策略也失败: {e},模块: {module}",
LOGGER_COMMAND,
session=session,
)
raise PermissionExemption(
"获取插件和用户数据超时,请稍后再试..."
)
except PermissionExemption:
# 如果是业务异常(如插件不存在),直接抛出
raise
except Exception as e:
logger.error(
f"获取插件和用户数据时发生未知错误: {e},模块: {module}",
LOGGER_COMMAND,
session=session,
)
if retry_count < MAX_RETRY_COUNT:
retry_count += 1
await asyncio.sleep(0.1 * retry_count)
else:
raise PermissionExemption(
f"获取插件和用户数据失败: {e},请稍后再试..."
)
# 确保plugin和user都已成功获取
if plugin is None or user is None:
raise PermissionExemption("获取插件和用户数据失败,请稍后再试...")
# 进入 hooks 并行检查区域(会在高并发时排队)
await _enter_hooks_section()
@@ -376,7 +495,7 @@ async def auth(
# 并行执行所有 hook 检查,并记录执行时间
hooks_start = time.time()
# 创建所有 hook 任务
# 创建所有 hook 任务(plugin和user已确保不为None)
hook_tasks = [
time_hook(auth_ban(matcher, bot, session, plugin), "auth_ban", hook_times),
time_hook(auth_bot(plugin, bot.self_id), "auth_bot", hook_times),
@@ -387,7 +506,9 @@ async def auth(
),
time_hook(auth_admin(plugin, session), "auth_admin", hook_times),
time_hook(
auth_plugin(plugin, group, session, event), "auth_plugin", hook_times
auth_plugin(plugin, group, session, event),
"auth_plugin",
hook_times,
),
time_hook(auth_limit(plugin, session), "auth_limit", hook_times),
]