← 所有文章
指南

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 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 跑通發送,再用標準事件將所有入站訊息接返去業務系統。