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

Node.JS+MongoDB Messenger聊天机器人:如何限制Facebook请求速率?

我之前也做过类似的Messenger聊天机器人,刚好碰到过和你一样的Facebook速率限制问题——超了40次/秒就触发High-Volume模式,禁用Page Inbox确实很头疼。结合你用Node.js+MongoDB的技术栈,给你几个轻量简洁、不用Redis的方案:

方案1:基于MongoDB的令牌桶算法(推荐)

令牌桶是处理速率限制的经典方案,完全可以用MongoDB来实现共享的令牌状态,不管你是单实例还是多实例部署都能生效。核心逻辑是:

  • 每秒自动生成40个令牌(对应Facebook的上限)
  • 每次发送请求前先从桶里取1个令牌,有令牌就执行请求,没有就等待或重试

代码示例

先创建一个rate-limiter.js:

const { MongoClient } = require('mongodb');

class MongoRateLimiter {
  constructor() {
    this.client = new MongoClient(process.env.MONGODB_URI);
    this.db = this.client.db('your-db-name');
    this.collection = this.db.collection('rate-limit-buckets');
    // 初始化桶(如果不存在的话)
    this.initBucket();
  }

  async initBucket() {
    const exists = await this.collection.findOne({ name: 'messenger-api' });
    if (!exists) {
      await this.collection.insertOne({
        name: 'messenger-api',
        tokens: 40, // 初始填满40个令牌
        lastRefillTime: Date.now()
      });
    }
  }

  async takeToken() {
    const now = Date.now();
    // 原子更新令牌桶:计算应该补充的令牌数,然后减去1个
    const result = await this.collection.findOneAndUpdate(
      { name: 'messenger-api' },
      [
        {
          $set: {
            // 计算从上次补充到现在应该生成的令牌数:(当前时间-上次时间)/1000 *40,最多补到40
            tokens: {
              $min: [
                40,
                {
                  $add: [
                    '$tokens',
                    {
                      $multiply: [
                        { $divide: [{ $subtract: [now, '$lastRefillTime'] }, 1000] },
                        40
                      ]
                    }
                  ]
                }
              ]
            },
            lastRefillTime: now
          }
        },
        // 尝试减去1个令牌,只有当令牌数>=1时才成功
        { $set: { tokens: { $subtract: ['$tokens', 1] } } }
      ],
      { returnDocument: 'after', upsert: true }
    );

    // 如果更新后的令牌数>=0,说明拿到了令牌
    return result.value.tokens >= 0;
  }

  // 封装一个等待令牌的方法
  async waitForToken() {
    while (true) {
      const hasToken = await this.takeToken();
      if (hasToken) return;
      // 没拿到令牌就等10ms再重试
      await new Promise(resolve => setTimeout(resolve, 10));
    }
  }
}

module.exports = new MongoRateLimiter();

然后在你的Messenger请求逻辑里用:

const rateLimiter = require('./rate-limiter');

async function sendMessengerMessage(recipientId, message) {
  // 先等拿到令牌
  await rateLimiter.waitForToken();
  // 然后发送请求到Facebook API
  const response = await fetch(`https://graph.facebook.com/v18.0/me/messages?access_token=${process.env.FB_ACCESS_TOKEN}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      recipient: { id: recipientId },
      message: { text: message }
    })
  });
  return response.json();
}

这个方案的好处是:

  • 完全基于MongoDB,不需要额外依赖
  • 支持多实例部署,因为令牌状态存在数据库里
  • 精确控制速率,不会超过40次/秒
方案2:队列+setInterval的批量处理(适合非实时场景)

如果你的消息发送允许少量延迟,可以把所有请求放到一个队列里,用setInterval每秒取出最多40个请求执行。这个方案超级轻量,单实例下完全够用。

代码示例

const messageQueue = [];
const MAX_PER_SECOND = 40;

// 每秒处理一次队列
setInterval(async () => {
  const batch = messageQueue.splice(0, MAX_PER_SECOND);
  if (batch.length === 0) return;
  // 批量执行请求(这里可以用Promise.all,但要注意如果某个请求失败的处理)
  await Promise.all(batch.map(async (task) => {
    try {
      await sendMessengerMessage(task.recipientId, task.message);
    } catch (err) {
      // 失败的话可以重新放回队列末尾
      messageQueue.push(task);
      console.error('发送消息失败,重新入队:', err);
    }
  }));
}, 1000);

// 封装入队方法
function queueMessengerMessage(recipientId, message) {
  messageQueue.push({ recipientId, message });
}

// 实际调用的时候用这个方法
queueMessengerMessage('user123', 'Hello there!');

这个方案的优势是实现简单,几乎没有代码量,但缺点是:

  • 单实例友好,多实例的话需要把队列放到MongoDB里(比如用一个集合存待发送的消息)
  • 消息会有最多1秒的延迟,不适合要求实时回复的场景
方案3:Agenda + 自定义速率控制

你提到Agenda基于MongoDB但不支持速率限制,其实可以给Agenda加一层自己的速率控制逻辑:

  1. 用Agenda存储所有待发送的消息任务
  2. 每次启动一个定时任务,每秒从Agenda里取出最多40个任务执行
  3. 执行完后标记任务为完成

代码示例

首先初始化Agenda:

const Agenda = require('agenda');
const agenda = new Agenda({ db: { address: process.env.MONGODB_URI, collection: 'agenda-jobs' } });

// 定义发送消息的任务
agenda.define('send messenger message', async (job) => {
  const { recipientId, message } = job.attrs.data;
  await sendMessengerMessage(recipientId, message);
});

// 每秒启动一次批量处理
agenda.every('1 second', 'process messenger queue', {}, {
  skipImmediate: true,
  priority: 'high'
});

// 处理队列的任务
agenda.define('process messenger queue', async () => {
  // 找到最多40个未处理的发送任务
  const jobs = await agenda.jobs({ name: 'send messenger message', status: 'waiting' }, { limit: 40 });
  // 逐个执行
  for (const job of jobs) {
    await job.run();
  }
});

// 启动Agenda
(async () => {
  await agenda.start();
})();

// 提交任务的方法
async function createMessengerJob(recipientId, message) {
  await agenda.schedule('now', 'send messenger message', { recipientId, message });
}

这个方案适合需要持久化任务(比如服务器重启后任务不会丢失)的场景,结合Agenda的任务管理,再加上自己的批量处理逻辑,就能实现速率限制。


内容的提问来源于stack exchange,提问作者baohiep

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 15:57:37