Discord.js PUBG数据统计Bot批量处理时无报错离线问题排查
问题概述
- 基于Discord.js开发的PUBG对局统计Bot,核心功能为对局数据分析、根据玩家数据自动分配服务器角色
- 新增休闲模式对局检测逻辑:拉取玩家最近25场对局记录,检测对局内AI玩家占比是否超过设定阈值
- 小范围玩家测试时功能运行正常,批量处理约200名服务器成员数据时,Bot会无任何错误提示直接离线
- 部署环境为Replit付费高级账号,手动重启可临时恢复服务,下一轮检测循环执行时会再次出现离线问题
- 已完成排查:未触发Discord官方接口频率限制,开启Discord.js debug事件监听未捕获到任何报错日志
问题代码
Dicord_Bot.on("debug", (e) => console.log(e)) exports.run = async (bot, message, args) => { var membersArray = message.guild.members.cache; const insertInto = bot.dbsql2.prepare('INSERT INTO testo(nickname, pubg_id, match_id , assists, damageDealt, headshotKills, kills, win) VALUES (?,?,?,?,?,?,?,?)'); for (let [snowflake, guildMember] of membersArray) { if (guildMember.user.bot) { continue; } var nickname = guildMember.nickname; if (!nickname) nickname = guildMember.user.username; console.log("Sprawdzam " + nickname); var matchesList = bot.pubgAPI.getPlayerMatchesList(bot, nickname); var pubgID = bot.pubgAPI.getPlayerID(bot, nickname); if(pubgID === 0 || pubgID === -1) { continue; } for (let i = 0; i <= 25; i++) { var participants = 0; var bots = 0; var players = 0; var lastStats = [0, 0, 0, 0, 0, 0, 0]; if (matchesList[i] === undefined) { console.log("Brak meczu"); continue; } var matchStats = bot.pubgAPI.getMatchStats(bot, matchesList[i].id) if (matchStats === undefined) { console.log("Brak stat"); continue; } if (matchStats.data === undefined) { console.log("Brak daty"); continue; } if (matchStats.data.attributes === undefined) { console.log("Brak atrybutow"); continue; } if (matchStats.data.attributes.stats === undefined) { console.log("Brak statow"); continue; } if (matchStats.data.attributes.gameMode === "squad" && matchStats.data.attributes.matchType === "official") { for (let j = 0; j < matchStats.included.length; j++) { if (matchStats.included[j] === undefined) { continue; } if (matchStats.included[j].attributes === undefined) { continue; } if (matchStats.included[j].attributes.stats === undefined) { continue; } if (matchStats.included[j].type === "participant") { participants++; if (matchStats.included[j].attributes.stats.playerId.includes("ai.")) { bots++; } else { players++; } } if (matchStats.included[j].attributes.stats.playerId === pubgID) { lastStats[0] += matchStats.included[j].attributes.stats.assists lastStats[1] += matchStats.included[j].attributes.stats.damageDealt lastStats[2] += matchStats.included[j].attributes.stats.headshotKills lastStats[3] += matchStats.included[j].attributes.stats.kills if (matchStats.included[j].attributes.stats.winPlace === 1) { lastStats[6] += 1 } else { lastStats[4] += 1 } lastStats[5] += 1 } } if (((bots / participants) >= 0.50)){ var returnMessage = "**" + nickname + "**" + " miał " + ((bots / participants) * 100).toFixed(2) + "% botów."; returnMessage += "\n**Usuwamy ze statystyk:**"; returnMessage += "\nDamage:" + lastStats[1].toFixed(2) returnMessage += "\nAsysty:" + lastStats[0] returnMessage += "\nHeadshoty:" + lastStats[2] returnMessage += "\nKille:" + lastStats[3] if (lastStats[6] != 0) { returnMessage += "\nWin:1" } bot.channels.cache.get("952186890499530792").send(returnMessage); insertInto.run(nickname,pubgID,matchesList[i].id,lastStats[0],lastStats[1],lastStats[2],lastStats[3],lastStats[6]) } } var sleep1 = await bot.sleepFor(10); } var sleep = await bot.sleepFor(1000) } } exports.help = { name: "test", channel: "952186890499530792", members: ["248165905119313948"] }
根因定位
- 未捕获异步请求异常:所有PUBG API调用(
getPlayerMatchesList/getPlayerID/getMatchStats)均未加await,拿到的只是pending状态的Promise对象而非实际返回数据。一旦API请求超时、触发限流、返回服务端错误,会抛出未捕获的Promise Rejection,Node.js默认遇到未捕获的异步异常会直接终止进程。现有逻辑只监听了Discord.js的debug事件,完全捕获不到进程级别的异常,所以看不到任何报错日志就直接离线。 - 请求间隔不符合PUBG API限流规则:当前设置单场对局查询仅间隔10ms,单用户查询仅间隔1s。按200个用户、每个用户25场对局计算,总请求量超过5400次,现有间隔下跑完一轮仅需70余秒,平均每秒发起70+次请求,远超过PUBG官方API每分钟10次的免费调用限额,会被服务端直接断开连接,触发连接错误杀进程。
- 循环边界错误:对局遍历循环写为
i <= 25,实际会执行26次循环,而玩家最近25场对局的数组下标范围是0-24,第26次取值永远为undefined,属于冗余无效逻辑。 - 内存溢出风险:一次性遍历全量服务器成员,所有API返回的对局数据全部驻留内存未释放,单场对局包含100名参与者的全量数据,200个用户的批量任务跑1-2轮就会超过Replit实例的内存上限,被系统OOM强杀,这也是无日志直接退出的典型特征。
- 逻辑阈值不匹配:代码中AI占比判断阈值写为0.5(即50%),和需求中设计的85%阈值不符。
修复方案
- 首先补上进程级全局错误监听,先把所有隐藏错误打印出来,方便后续排查:
// 放在Bot初始化代码的最顶部 process.on('unhandledRejection', (reason, promise) => { console.error('未捕获异步异常:', reason) }) process.on('uncaughtException', (err) => { console.error('未捕获进程异常:', err) })
- 所有PUBG API异步调用必须加
await关键字,拿到真实返回值后再做后续逻辑判断:
// 修正前 // var matchesList = bot.pubgAPI.getPlayerMatchesList(bot, nickname); // var pubgID = bot.pubgAPI.getPlayerID(bot, nickname); // var matchStats = bot.pubgAPI.getMatchStats(bot, matchesList[i].id) // 修正后 var matchesList = await bot.pubgAPI.getPlayerMatchesList(bot, nickname); var pubgID = await bot.pubgAPI.getPlayerID(bot, nickname); var matchStats = await bot.pubgAPI.getMatchStats(bot, matchesList[i].id)
- 调整请求间隔适配PUBG API限流规则:单场查询间隔从10ms调整为至少600ms,单用户查询间隔从1s调整为2s以上;额外增加429/5xx错误重试逻辑,遇到限流或服务端错误自动等待3-5s后重试,避免直接抛错终止进程。
- 修正循环边界:将对局遍历条件从
i <= 25改为i < 25,去掉无效的第26次循环。 - 分批处理用户数据:每处理10个用户就等待10s,处理完单场对局后及时释放无用的对局数据引用,避免内存堆积触发OOM。
- 将AI占比判断阈值从0.5调整为需求要求的0.85。
内容的提问来源于stack exchange,提问作者kotkuslodku
相关产品推荐
相关产品推荐

