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加一层自己的速率控制逻辑:
- 用Agenda存储所有待发送的消息任务
- 每次启动一个定时任务,每秒从Agenda里取出最多40个任务执行
- 执行完后标记任务为完成
代码示例
首先初始化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
相关产品推荐
相关产品推荐

