You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord.js PUBG数据统计Bot批量处理时无报错离线问题排查

问题概述
  • 基于Discord.js开发的PUBG对局统计Bot,核心功能为对局数据分析、根据玩家数据自动分配服务器角色
  • 新增休闲模式对局检测逻辑:拉取玩家最近25场对局记录,检测对局内AI玩家占比是否超过设定阈值
  • 小范围玩家测试时功能运行正常,批量处理约200名服务器成员数据时,Bot会无任何错误提示直接离线
  • 部署环境为Replit付费高级账号,手动重启可临时恢复服务,下一轮检测循环执行时会再次出现离线问题
  • 已完成排查:未触发Discord官方接口频率限制,开启Discord.js debug事件监听未捕获到任何报错日志

Bot离线问题截图

问题代码
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"]
}
根因定位
  1. 未捕获异步请求异常:所有PUBG API调用(getPlayerMatchesList/getPlayerID/getMatchStats)均未加await,拿到的只是pending状态的Promise对象而非实际返回数据。一旦API请求超时、触发限流、返回服务端错误,会抛出未捕获的Promise Rejection,Node.js默认遇到未捕获的异步异常会直接终止进程。现有逻辑只监听了Discord.js的debug事件,完全捕获不到进程级别的异常,所以看不到任何报错日志就直接离线。
  2. 请求间隔不符合PUBG API限流规则:当前设置单场对局查询仅间隔10ms,单用户查询仅间隔1s。按200个用户、每个用户25场对局计算,总请求量超过5400次,现有间隔下跑完一轮仅需70余秒,平均每秒发起70+次请求,远超过PUBG官方API每分钟10次的免费调用限额,会被服务端直接断开连接,触发连接错误杀进程。
  3. 循环边界错误:对局遍历循环写为i <= 25,实际会执行26次循环,而玩家最近25场对局的数组下标范围是0-24,第26次取值永远为undefined,属于冗余无效逻辑。
  4. 内存溢出风险:一次性遍历全量服务器成员,所有API返回的对局数据全部驻留内存未释放,单场对局包含100名参与者的全量数据,200个用户的批量任务跑1-2轮就会超过Replit实例的内存上限,被系统OOM强杀,这也是无日志直接退出的典型特征。
  5. 逻辑阈值不匹配:代码中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:09:20