Node.js使用Telegraf批量发送Telegram消息触发限流致服务崩溃
问题概述
Node.js服务基于Telegraf实现Telegram Bot告警推送时,流处理场景下数秒内会完成近百个条件校验,命中条件即触发消息发送。当每秒发送量超过30条时会触发Telegram Bot接口限流,严重时导致服务崩溃。
之前尝试的两种setTimeout方案均存在问题:
- 固定500ms延迟发送的方案:批量调用时所有定时器几乎同时注册,到期时间差极小,延迟到期后消息依然集中发出,无法打散流量
- 直接将setTimeout返回值赋值给sendToTGBot的方案:sendToTGBot不再是可执行函数,且参数无法正常传递,直接抛出运行错误
最优实现方案:带流控的先进先出消息队列
核心逻辑是将所有待发送告警先存入内存队列,再按照预设的安全速率匀速从队列中取消息发送,从根本上避免瞬时流量洪峰触发限流,同时内置失败重试、限流自动避让逻辑,适配后续告警量增长场景。
// 待发消息队列 const messageQueue = [] // 安全发送配置:每秒25条,单条间隔40ms,低于Telegram 30条/秒的限流阈值,预留冗余 const SEND_INTERVAL = 40 let isProcessing = false // 业务侧直接调用的发送方法 export const sendToTGBot = (alert) => { // 所有消息先入队,不直接调用发送接口 messageQueue.push(alert) // 队列未处于发送状态时启动处理循环 if (!isProcessing) { processQueue() } } // 队列匀速处理逻辑 const processQueue = async () => { isProcessing = true while (messageQueue.length > 0) { const currentAlert = messageQueue.shift() try { await bot.telegram.sendMessage(chat_id, currentAlert) } catch (err) { console.error('Telegram消息发送失败,即将重试:', err.message) // 发送失败的消息放回队头,避免丢失 messageQueue.unshift(currentAlert) // 命中限流时按接口返回的等待时间避让 if (err.code === 429) { const waitTime = err.parameters?.retry_after || 1 await new Promise(resolve => setTimeout(resolve, waitTime * 1000)) } } // 固定间隔后再发送下一条,保证速率平稳 await new Promise(resolve => setTimeout(resolve, SEND_INTERVAL)) } isProcessing = false }
方案特性
- 速率平稳可控:无论瞬时涌入多少告警,发送速率始终稳定在配置的阈值内,不会出现集中发送触发限流的问题。后续业务量增长只需调整
SEND_INTERVAL参数即可适配,无需修改核心逻辑。 - 消息可靠性保障:发送失败的消息会自动放回队头重试,不会丢失告警;当接口返回429限流错误时,会自动读取接口返回的等待时间避让,避免持续请求触发更严格的限流。
- 轻量无依赖:纯原生JavaScript实现,无需引入第三方中间件或库,可直接嵌入现有业务代码。
- 性能开销低:仅当队列存在待发消息时才会运行发送循环,队列清空后自动退出,不会空转消耗服务资源。
可选扩展方向
- 多会话隔离:如果需要向多个不同chat_id发送消息,可按chat_id维度维护独立的发送队列,避免不同会话的消息发送互相阻塞。
- 优先级支持:如果存在不同等级的告警,可将普通FIFO队列替换为优先级队列,高优先级告警插入队头优先发送。
- 消息合并:短时间内产生的大量同类型告警可合并为单条消息发送,进一步降低接口请求频次。
- 积压监控:新增队列长度统计逻辑,当队列积压超过预设阈值时触发高优告警,方便及时调整发送速率或扩容。
内容的提问来源于stack exchange,提问作者Varun
相关产品推荐
相关产品推荐

