如何修复discord.py机器人无报错但响应延迟的运行异常问题
问题根因分析
- 同步数据库查询阻塞异步事件循环:你使用的
logs_info.find_one是MongoDB同步驱动的查询方法,属于同步阻塞操作。discord.py完全基于异步事件循环运行,任何同步阻塞操作都会卡住整个事件循环的执行,期间所有指令接收、事件处理、消息回复都会暂停。你的旧机器人在86个服务器运行,on_member_update会在任意服务器成员的昵称、状态、角色等属性变更时触发,单日触发量可达数千甚至数万次,大量阻塞操作堆积后就会出现几分钟才回复指令的情况,甚至删除代码后短时间内还是卡顿,是因为事件循环中还有大量堆积的未处理事件,重启进程即可立刻消除堆积。新机器人因为加入的服务器少,事件触发量极低,阻塞的负面影响没有暴露,所以运行正常,和token本身没有任何关系。 - 无效事件无前置过滤:
on_member_update会响应所有成员属性变更事件,你当前的逻辑仅需要处理角色变更场景,但代码是先执行数据库查询,再判断角色是否变化,90%以上的数据库查询都是无效的,额外增加了大量阻塞开销。 - 角色差集计算效率低:使用遍历列表的方式查找变更的角色,时间复杂度为O(n²),成员角色数量较多时会额外增加执行耗时。
- 重复初始化时区对象:每次事件触发都重新实例化
pytz.timezone('Asia/Kolkata'),属于不必要的重复操作。
优化方案
- 替换为异步数据库驱动:将MongoDB同步驱动替换为异步驱动
motor,使用异步查询方法await logs_info.find_one(...),避免阻塞事件循环,仅需修改连接逻辑和查询前加await,不需要调整已有数据结构。 - 增加事件前置过滤:进入逻辑后首先判断
before.roles == after.roles,如果角色没有变化直接返回,不需要执行后续的数据库查询等操作。 - 优化角色差集计算:使用集合求差的方式查找变更角色,时间复杂度降到O(n)。
- 全局初始化时区对象:将时区实例化操作移到Cog类的初始化方法或者全局变量中,仅初始化一次即可。
- 增加日志通道存在性判断:获取到日志通道后提前判断是否为None,避免后续发送消息时报错。
优化后代码示例
import pytz from datetime import datetime import discord from discord.ext import commands from random import randint # 全局初始化时区,仅执行一次 IST = pytz.timezone('Asia/Kolkata') class YourCogName(commands.Cog): def __init__(self, bot): self.bot = bot # logs_info在这里初始化motor异步集合 @commands.Cog.listener() async def on_member_update(self, before, after): # 前置过滤:角色没变化直接返回,跳过所有后续逻辑 if before.roles == after.roles: return # 只在角色有变化时才执行查询 try: server_id = after.guild.id # 异步查询,加await log_config = await logs_info.find_one({'id': server_id}) if not log_config or 'action_logs' not in log_config: return channel_id = int(log_config['action_logs']) logchannel = self.bot.get_channel(channel_id) if not logchannel: return except Exception: return time_now = datetime.now(IST).strftime('%Y-%m-%d %H:%M:%S') # 用集合求差集找变更角色,效率更高 if len(before.roles) < len(after.roles): new_role = next(iter(set(after.roles) - set(before.roles))) embed = discord.Embed( title="角色已添加", description=f"已为 {after.mention} 授予角色 {new_role.mention}", colour=randint(0, 0xffffff) ) embed.add_field(name="添加时间", value=time_now) else: removed_role = next(iter(set(before.roles) - set(after.roles))) embed = discord.Embed( title="角色已移除", description=f"已从 {after.mention} 移除角色 {removed_role.mention}", colour=randint(0, 0xffffff) ) embed.add_field(name="移除时间", value=time_now) embed.set_thumbnail(url=after.guild.icon.url if after.guild.icon else None) embed.set_footer(text=self.bot.user.name, icon_url=self.bot.user.avatar.url if self.bot.user.avatar else None) embed.set_author(name=after.name, icon_url=after.avatar.url if after.avatar else None) await logchannel.send(embed=embed)
内容的提问来源于stack exchange,提问作者Daniel Jebarson K
相关产品推荐
相关产品推荐

