← 所有文章

FIELD NOTES / PLC 程式與控制

兩來源同時更新同一資料如何仲裁

PLC 程式與控制作者:站長預估 6 分鐘閱讀

以中央權威版本、CAS與單一寫入者仲裁兩來源更新,示範A/B同持v7時僅一方形成v8,並處理重試、epoch與fencing。

本文目錄

先定義誰是資料的裁判

兩個來源同時修改同一筆配方、設定或狀態時,不能用「最後收到的訊息」猜誰應該勝出。網路延遲、重送和不同來源時鐘都可能讓較舊內容晚到。先指定中央權威資料庫或服務,所有寫入都經過它判斷;A與B是請求者,不直接互相覆寫。

本篇用版本號與CAS(compare-and-set)示例。資料目前是v7,讀取者提交更新時必須帶expected_version=7。中央服務只有在目前仍為v7時,才以一次原子操作寫入新payload並產生v8;條件不成立就回覆衝突,讓呼叫者重新讀取,不把舊內容強行蓋上去。

CAS的版本是中央資料的序列,不是來源自己填的時間或流水號。A與B都持v7時,A先成功形成v8;B稍後仍拿v7來寫,條件已不成立,因此拒絕。成功者要回傳新版本和寫入結果,失敗者要能辨認conflict與格式錯誤、權限錯誤的差異。

單一寫入者可在中央服務內實現串行化決策,但「單一」指同一資料的權威寫入路徑,不代表現場一定只有一台電腦。備援切換時還要有epoch或fencing token,使舊主即使恢復連線也不能繼續寫入。這是架構概念,不能宣稱Q系列PLC原生提供CAS或fencing。

用v7案例走過成功與拒絕

初始資料是key=recipe-R7、version=7、payload=P7。A讀到v7並提出P8A,B也讀到v7並提出P8B。中央先收到A,檢查權威版本仍是7,原子地保存P8A並回傳version=8。接著B的expected_version仍是7,中央回傳VersionConflict;P8B不得成為目前值。

B收到衝突後可重新讀取v8,向使用者顯示A已先更新,再由使用者合併或重新提出以v8為前提的P9B。若業務規則允許欄位級合併,也要由中央服務依明確規則產生新版本;不能由B自行把v7與v8拼接後假定沒有衝突。

版本相等只能表示兩次讀取看到了同一中央序列點,無法證明跨來源誰比較新。來源A的時鐘10:00與B的時鐘10:05不能拿來last timestamp wins,因為時區、校時、延遲和故障都會破壞這個假設。若需要事件時間,保存它作為描述欄位,不用它取代CAS裁決。

每次請求要有request_id、expected_version、payload摘要、來源識別與中央回覆。成功重試同一request_id時,中央應回傳原先結果;若同一request_id卻帶不同payload,回報IdempotencyConflict並拒絕,避免逾時重送造成第二次不同寫入。

重試 主備與fencing

請求送出後逾時不等於寫入失敗。呼叫者先用同一request_id查詢結果或重送完全相同的payload;不可因沒有收到回覆就換一個request_id再寫。中央保存足夠的冪等紀錄期限,期限和容量要由系統規格決定,不能在本文任意給出設備通用值。

主備切換時,新的主服務取得較大的epoch或fencing token,寫入層檢查token仍是目前有效者。舊主即使網路恢復,提交舊epoch也應被拒絕。這需要中央儲存、鎖或代理層共同實作;PLC的掃描週期、資料寄存器或一般互鎖不能自動等同分散式fencing。

若權威服務本身不可用,來源可進入pending而不是各自建立兩個v8。待恢復後重新以expected_version仲裁,並保留來源原始請求。若產品要求離線操作,需另定衝突檔案、人工合併和回放順序,不能把離線時鐘當成跨來源真實順序。

正常結果是中央只有一個v8,目前payload可查、A的請求成功、B的請求明確衝突。失敗排查先看中央版本、expected_version、request_id和epoch,再看傳輸重試;不要先把所有衝突改成「最後到達者成功」,那會讓問題更難重現。

同一次重試要保留request_id、expected_version和完整內容,不能只固定識別碼卻更新前提版本。中央應先查已完成請求紀錄,再判斷新請求的版本條件;否則已成功的A重送時,會被自己造成的v8誤判為衝突。請求結果與資料更新也需一致保存。

驗收與適用限制

離線驗收至少測:A/B同持v7、A先到、B先到、同request_id重送、同request_id換payload、逾時後查詢、主備epoch變更、來源時鐘故意偏移,以及中央版本已被第三者更新。每列記錄預期版本、勝負、回覆代碼和目前payload摘要。

CAS只保護「以某版本為前提的整體更新」。它不會自動判斷兩個欄位是否可安全合併,也不會檢查製程互鎖、權限或配方工程範圍。中央服務仍要在CAS前做schema、權限和業務檢查;成功版本也不等於已寫入控制器或設備已採用。

若資料分散在多個權威系統,先收斂成一個可裁決的版本來源,或設計明確的交易與事件順序。不要讓A資料庫和B資料庫各自發號v8再互相覆蓋。事件日誌要保存舊版本、新版本、來源、request_id、epoch與原因,支援事後重播。

衝突回覆要讓操作員看懂:顯示目前中央版本、提出者看到的版本、目前payload摘要與下一步重新讀取建議。不要只回傳一個模糊的寫入失敗。自動重試也要有次數與退避上限,並在達到上限時保留原請求供人工決定。

版本號應由中央單調產生,並與資料內容一同提交。若中央回覆v8卻找不到對應payload,表示儲存或讀取流程不一致,應進入故障狀態而不是讓來源自行補寫。讀取也可回傳版本與摘要,協助呼叫者確認自己拿到的是同一份資料。

事件紀錄要可區分拒絕與未到達中央的請求。沒有中央回覆時標為unknown,查詢後才決定成功或失敗,避免把網路超時誤記成安全拒絕。

若更新包含數值設定,CAS成功前仍要驗證單位、上下限和互鎖;例如兩個來源各自修改不同欄位,也可能合併後違反整體製程條件。中央裁決的是版本順序,不是安全審查的替代品。

本文提供分散式資料設計與案例。實際Q系列PLC專案若需要寫入,仍須依目標CPU、通訊模組、權限和現場安全程序另行驗證。

常見問題

問:B的時間比較晚,能否直接勝出?答:不能;不同來源時鐘無法可靠決定新舊,應以中央CAS版本裁決。

問:A與B都從v7讀取,是否可以都產生v8?答:中央只能接受一個符合expected_version=7的原子更新,另一個應回報衝突。

問:逾時後換request_id重送比較安全嗎?答:不安全;保留同request_id與完全相同payload,才能辨認原請求是否已成功。

問:epoch與fencing是Q PLC內建功能嗎?答:本文只說明架構概念,不能據此宣稱Q PLC原生支援,須由專案的中央服務與寫入層實作。

參考:IETF HTTP條件請求規範,說明ETag與If-Match可用於避免以過期表示覆蓋目前表示;本文CAS與版本流程是資料設計延伸,不是PLC API。

參考:PostgreSQL官方文件的交易隔離與序列化概念,可作為中央原子更新與衝突重試的資料庫參考;實際系統仍須核對所用儲存層。

延伸閱讀