先接受回覆可能遺失這件事
接收端已把一筆產量存入資料庫,回覆卻在網路中遺失。發送端等不到確認,只能重試;如果接收端每收到一次就新增一次,產量會被算成兩件。要修正的是資料身分與寫入流程,而不是禁止所有重試,否則真正未存入的資料又會漏掉。
為每個業務事件建立穩定的唯一識別,第一次發送前就保存,後續重送沿用同一個鍵。傳送次數、接收時間與連線編號可以每次不同,但不能拿它們取代事件身分。每次重試重新產生事件鍵,資料庫就無法判斷兩筆其實是同一件事。
本文適用於閘道或服務接收資料後寫入資料庫的流程,範例以PostgreSQL 18的唯一約束與衝突處理為參考。不假設PLC直接支援資料庫交易,也不提供未核對的通訊模組指令;PLC、閘道與服務各自能保存什麼狀態,仍須依實際平台確認。
先區分事件與目前狀態。事件重送不應新增第二次,設備目前狀態則可能被較新的版本更新。兩者使用同一種「遇到相同設備就覆寫」策略,容易把歷史事件刪掉;只用設備編號當唯一鍵,也會擋住同一台設備的下一件正常產量。
用三次傳送驗證一次入庫
設定教學事件為來源A、啟動世代E7、序號42,內容是合格一件。第一次接收並提交,模擬確認遺失;第二次與第三次使用完全相同事件鍵重送。正確結果是事件表一列、業務產量增加一件、傳送嘗試記錄三次。
來源與啟動世代必須能區分序號重新開始。若設備重新開機後序號又從一開始,而世代也回到相同值,新事件可能被當成舊事件丟掉。世代應由可靠的持久設計或協調機制定義,不能只拿接收端每次重連產生的編號充當。
把事件鍵相關欄位設為必填,並建立真正的唯一限制。只在應用程式先查「不存在」再新增,兩個接收工作者可能同時查到不存在而各插一筆;資料庫的限制才是併發時的最後防線。欄位允許空值時的唯一行為也要依產品核對。
PostgreSQL的ON CONFLICT可在指定唯一衝突時採不插入或更新,但它不會替你決定事件語意。相同鍵、相同有效內容可視為重送;相同鍵卻不同產量或設備,應列為內容衝突並保留證據,不要無條件用後來資料覆蓋。
比對內容時排除每次傳送都會改變的接收時間等欄位,保留真正代表事件的來源、數值、單位與版本。若使用內容摘要,先定義欄位順序與正規化規則;摘要不同要能追到原始差異,不能只留一段無法解讀的雜湊值。
把業務更新與去重記錄放進同一範圍
假設事件表已擋住重複,但程式每次收到事件仍把總產量加一,結果還是重複。只有首次接受的事件才能觸發對應業務更新;事件接受記錄與資料庫內的產量更新應在同一交易中完成,失敗時一起回復,避免只完成半套。
交易提交成功後才回覆已接收。若提交後服務中斷,發送端重試時查到同一事件,回覆先前已接受的結果即可。若提交結果不確定,應以同一鍵確認或重試,不能先換一個新鍵再送,因為那會失去原本的去重保護。
資料庫交易不會自動包住寄信、呼叫外部系統或控制設備。需要外送通知時,可在同一交易保存待送項目,再由獨立流程發送;外部接收者仍須有自己的去重策略。不要把本地唯一限制宣稱為所有系統都只執行一次。
回覆應區分首次接受、已接受的重送、內容衝突與暫時失敗。暫時失敗可依策略延後重試;格式錯誤或鍵內容衝突反覆重送通常不會自行改善,應送入待處理佇列並保留原因,不讓一筆壞資料阻塞全部新資料。
去重保存期限必須涵蓋可能重送的最長期間。若事件內容封存後連去重鍵也刪掉,幾個月前的備份重新補送就可能再次入庫。保存精簡接受紀錄或限制可補送範圍,都要有明確契約與查核方式。
從結果 併發與故障位置做驗收
先跑一筆正常事件,再測同鍵重送、同鍵異內容、兩個工作者同時送同鍵,以及提交前後中斷。每次核對事件列數、業務總量、回覆狀態與待處理數量。只看到唯一鍵錯誤沒有新增列,還不能證明整個流程正確。
失敗時先查發送端重試是否沿用原鍵,再查唯一限制是否真的存在於接收表,接著查業務更新是否仍在去重流程外。若重複只出現在重開機後,優先查序號重置與世代保存;若只出現在封存後,查去重期限。
批次傳送也要逐事件確認。十筆中九筆已提交、一筆失敗時,重送整批可以安全的前提是每筆都有穩定鍵,不能只用整批檔名推定十筆全部完成。整批交易或逐筆交易各有取捨,先明訂回覆與重試的粒度。
完成後應能列出收到幾次、接受幾個唯一事件、重送幾次、衝突幾次及失敗幾次。這些計數的分類要互斥或註明重疊關係;不要用「收到一百筆、資料庫九十筆」直接推論漏十筆,先核對其中是否包含合法重送。
練習與常見問題
練習:收到十二次傳送,包含十個不同事件鍵,其中一個鍵額外重送兩次,內容全部一致。預期事件十列、重送兩次。接著用既有鍵送不同數值,事件列數仍十,另有一筆內容衝突待處理,原數值不能默默被換掉。
問:網路穩定就不需要唯一鍵嗎?答:服務重啟、回覆遺失與人工補送仍可能造成重試。
問:使用目前時間當鍵夠嗎?答:同時事件、時鐘調整與重送都會使這種設計失真。
問:DO NOTHING就是完整去重嗎?答:它只處理指定的插入衝突,還要核對內容與所有業務副作用。
問:重複鍵錯誤應直接丟棄嗎?答:先判斷相同事件重送或內容衝突,再回覆正確狀態並保留紀錄。
參考:PostgreSQL 18 INSERT:ON CONFLICT與併發寫入的定義。
參考:PostgreSQL 18 Transactions:交易的整體提交與回復。