← 所有文章
指南

Telegram getUpdates offset:避免重复处理与更新丢失

当你调用 getUpdates,且传入的 offset 大于某条更新的 update_id 时,Telegram 就会确认这条更新。收到响应本身不等于确认。要避免工作丢失,应先持久化本批更新,再发起使用更大 offset 的下一次请求;要安全应对重启,还需把下一次 offset 与更新一起保存,并将去重和业务处理分开。

要点

  • offset 是确认边界,不是页码或消息数量。
  • 只有即将被确认的更新全部安全落盘,才能推进检查点。
  • 每个机器人只保留一个活跃轮询者;需要扩容的是下游工作进程。
  • 持久化收件箱保护接收环节,但外部业务动作仍需独立设计重试。

本文假设你已经选定轮询。如果仍配置着 webhook,先参考 getUpdates 与 setWebhook 切换指南。这里解决的是两次成功轮询之间如何提交数据,而不是重新选择接收模式。

getUpdates offset 到底确认了什么

Telegram 官方 Bot API 文档offset 定义为要返回的第一条更新的标识符。不传时,从最早尚未确认的更新开始返回。后续调用的 offset 一旦超过某条更新的标识符,该更新便被确认。

**假设示例:**一次响应包含 810081018102。下一次传入 offset=8103,三条都会被确认,即使你的应用只处理了最后一条。Telegram 不会查看你的数据库,也不会等待 CRM 完成工作。

常见捷径风险更稳妥的设计
保存整批数据前,先记录最大的更新 ID重启后可能跳过根本没保存的更新收件箱与下一次 offset 一起提交
最快的并行任务完成就推进尚未完成的较早更新也可能被确认根据持久化进度,而非任务完成顺序推进
只在内存里保存 offset重启后丢失本地检查点读取持久化的机器人级检查点
用负 offset “修复”重复丢弃队列中更早的更新检查轮询所有权和去重机制

Telegram 明确说明,负 offset 从队列末尾开始取更新,并遗忘更早的更新。它不是恢复仍有业务价值的数据的方法。

将接收进度与业务完成状态分开

建议维护两类应用自己的记录:保存完整更新的收件箱,以及保存下一次 offset 的检查点。这些是本地存储概念,不是 Telegram 新增字段。

收件箱的唯一键可由机器人的稳定身份和 update_id 组成。不要把 bot token 本身作为数据库键,也不要写入日志。保存所有返回的更新类型,包括当前工作进程尚不认识的类型;分类可以在接收之后进行。

推荐的事务顺序:

读取该机器人已保存的下一次 offset。
使用该 offset 调用 getUpdates。
若返回空批次,保持检查点不变。
否则开启数据库事务:
  插入每一条更新;已存在的唯一键不重复插入。
  将本批 max(update_id) + 1 保存为下一次 offset。
提交事务。
只有提交成功后,才发起下一次轮询。
由独立工作进程处理已保存的更新。

这是设计伪代码,不是完整轮询客户端。它依赖单一活跃轮询者、持久化事务存储和唯一约束。如果事务失败,停止推进,从已保存的检查点重试;不要捕获存储错误后仍用更大的 offset 继续轮询。

如果收件箱和检查点位于不同系统,上述原子事务不会自动成立。应明确设计持久化交接与核对流程,不能把两次成功写入视为原子操作。

按崩溃边界做验收

下表是建议测试场景,不是实测结果:

中断位置应有的恢复行为
收到批次后、提交前读取旧检查点,允许重复接收
事务执行期间回滚后不能残留部分检查点更新
提交后、下一次轮询前读取新检查点,已保存的任务仍可处理
外部操作成功后、记录完成前对账或使用下游幂等能力;接收唯一键不能单独防止重复动作

重复更新应关联到已有收件箱记录,但这不代表业务已完成。工作进程仍需寻找并重试已保存但未完成的任务。反过来,重复收到更新也不应自动触发另一条回复或再次修改 CRM。

部署时明确移交轮询所有权。被遗忘的开发进程,或携带更大 offset 的人工诊断请求,都可能在你的存储流程之外确认更新。不要让所谓的“只读排查”推进生产进度。

限制与 UnifyPort 的边界

Telegram 官方说明,传入更新最多保留 24 小时。检查点不是无限期存档,调小 offset 也无法恢复已确认或已过期的更新。中断期间应按可能存在数据缺口调查。

如果出海团队需要连接现有账号,或合并 Telegram、WhatsApp、LINE 等渠道,应先阅读 Telegram Bot API webhook 与统一入站 webhook 对比。UnifyPort 的非官方接口使用 message.received 等标准事件,不使用机器人的轮询 offset。

UnifyPort 投递文档定义了独立的确认契约:配置 signing_secret,用 HMAC-SHA256 对时间戳、一个点和原始请求字节验签,验证 X-Device-Signature 后持久化事件,再返回成功响应。普通事件重试会复用 X-Device-Event-Id。UnifyPort 不提供 REST 消息历史读取 API,也不保证重放遗漏载荷,不能恢复已确认的 Bot API 更新。

常见问题

为什么 getUpdates 一直返回相同更新?

检查下一次请求是否真的使用了大于这些 update_id 的 offset,以及重启后是否读取了已保存的检查点。应安全去重,而不是清空队列。

必须等 AI 或 CRM 任务完成才能推进吗?

如果完整更新已持久化,且任务能独立重试,就不必等待。仅保存在内存里不算可靠交接。

这样能保证回复只发送一次吗?

不能。原子存储解决一类接收丢失问题,但发送可能已经成功,而工作进程尚未记录完成。该边界仍需幂等设计或对账。

下一步与来源

重点审查轮询循环中的数据库提交边界。如果接入的是消息账号接收端,请按 webhook 投递契约实现,不要搬用 Bot API offset 逻辑。

UnifyPort API

让消息接入变成一条稳定的产品管线。

先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。