持久化策略探索)
EAP狀態(tài)持久化策略探索在之前的優(yōu)化中我們將EAP啟動狀態(tài)section/zone是否已啟動從Dictionarystring, bool升級為線程安全的EapSectionStateManager基于HashSetstring lock。當(dāng)前實(shí)現(xiàn)是純內(nèi)存in-memory程序重啟后所有狀態(tài)重置為“未啟動”?,F(xiàn)在我們系統(tǒng)性地探索持久化Persistence策略目標(biāo)是讓EAP狀態(tài)在應(yīng)用崩潰、重啟、服務(wù)器重啟等場景下可恢復(fù)同時(shí)兼顧工業(yè)控制系統(tǒng)的安全性、實(shí)時(shí)性、簡潔性。1. 為什么需要持久化業(yè)務(wù)場景分析當(dāng)前痛點(diǎn)如果EAP主機(jī)Host異常重啟正在“已啟動”狀態(tài)的section會被重置導(dǎo)致下次S2F41 START消息可能重復(fù)觸發(fā)RemoteCallStartFlow或丟失流程狀態(tài)。GEM/SECS標(biāo)準(zhǔn)參考SEMI E30GEM明確要求Control StateOnline/Offline/Remote/Local等關(guān)鍵狀態(tài)必須保存在非易失性存儲non-volatile storage中重啟后狀態(tài)應(yīng)保持一致。EAP特有場景您的EapSectionStateManager是自定義的“遠(yuǎn)程啟動標(biāo)記”并非標(biāo)準(zhǔn)GEM狀態(tài)。因此持久化不是強(qiáng)制但能顯著提升系統(tǒng)魯棒性尤其在24×7產(chǎn)線環(huán)境中。潛在風(fēng)險(xiǎn)持久化后需考慮“狀態(tài)過期”e.g. 產(chǎn)線重啟后是否強(qiáng)制清零、數(shù)據(jù)一致性、文件/DB損壞恢復(fù)。結(jié)論推薦輕量級持久化而不是完全不持久化。優(yōu)先確保“安全重啟”。2. 常見持久化策略對比適合工業(yè)EAP/.NET環(huán)境策略實(shí)現(xiàn)復(fù)雜度性能/實(shí)時(shí)性可靠性適用場景優(yōu)缺點(diǎn)總結(jié)1. 純內(nèi)存當(dāng)前★☆☆☆☆極高低重啟丟失測試/簡單場景簡單但不滿足產(chǎn)線高可用要求2. JSON文件持久化最推薦★★☆☆☆高1ms中高原子寫備份多數(shù)EAP系統(tǒng)10~100個(gè)section輕量、無依賴、易部署。工業(yè)中最常用3. SQLite單文件數(shù)據(jù)庫★★★☆☆高高事務(wù)ACID需要審計(jì)/歷史記錄支持并發(fā)、查詢靈活。體積稍大4. 企業(yè)級數(shù)據(jù)庫SQL Server/PostgreSQL★★★★☆中極高已集成MES/CIM的大廠強(qiáng)大但運(yùn)維成本高5. Event Sourcing事件溯源★★★★★中極高需要完整審計(jì)軌跡高級方案可回放任意時(shí)刻狀態(tài)6. Redis/分布式緩存★★★☆☆極高高需集群多主機(jī)/分布式EAP適合云化/多實(shí)例成本較高工業(yè)控制最佳實(shí)踐基于SECS/GEM社區(qū)經(jīng)驗(yàn)優(yōu)先JSON文件無需額外服務(wù)部署簡單符合半導(dǎo)體EAP“輕量化”原則。必須原子寫寫臨時(shí)文件 → 重命名防止半途崩潰導(dǎo)致文件損壞。啟動時(shí)加載 校驗(yàn)損壞時(shí)自動回退為空狀態(tài)安全優(yōu)先。周期性或變更時(shí) 程序退出時(shí)保存。3. 推薦方案JSON文件持久化完整實(shí)現(xiàn)3.1 核心設(shè)計(jì)文件路徑AppDomain.CurrentDomain.BaseDirectory Config\\EapSectionState.json或固定路徑便于備份。序列化使用System.Text.Json.NET 6內(nèi)置高性能。擴(kuò)展性未來可存儲更多信息如StartedAt時(shí)間戳、啟動者。3.2 優(yōu)化后的EapSectionStateManager完整代碼usingSystem.Text.Json;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Threading;publicstaticclassEapSectionStateManager{privatestaticreadonlyHashSetstring_startedSectionsnewHashSetstring(StringComparer.OrdinalIgnoreCase);privatestaticreadonlyobject_locknewobject();privatestaticreadonlystring_stateFilePathPath.Combine(AppDomain.CurrentDomain.BaseDirectory,Config,EapSectionState.json);// 靜態(tài)構(gòu)造函數(shù)程序啟動時(shí)自動加載staticEapSectionStateManager(){LoadFromFile();}privatestaticvoidLoadFromFile(){try{if(!File.Exists(_stateFilePath))return;stringjsonFile.ReadAllText(_stateFilePath);varlistJsonSerializer.DeserializeListstring(json);if(list!null){lock(_lock){_startedSections.Clear();foreach(varsectioninlist)_startedSections.Add(section);}m_Log.Info($EAP狀態(tài)已從文件恢復(fù)共{list.Count}個(gè)section);}}catch(Exceptionex){m_Log.Error($EAP狀態(tài)文件加載失敗使用空狀態(tài):{ex.Message},ex);// 安全回退不拋異常}}privatestaticvoidSaveToFile(){try{Directory.CreateDirectory(Path.GetDirectoryName(_stateFilePath)!);Liststringlist;lock(_lock){listnewListstring(_startedSections);}stringjsonJsonSerializer.Serialize(list,newJsonSerializerOptions{WriteIndentedtrue});// 原子寫先寫臨時(shí)文件stringtempFile_stateFilePath.tmp;File.WriteAllText(tempFile,json);File.Move(tempFile,_stateFilePath,overwrite:true);m_Log.Debug($EAP狀態(tài)已持久化到文件 ({list.Count}個(gè)section));}catch(Exceptionex){m_Log.Error($EAP狀態(tài)保存失敗:{ex.Message},ex);}}publicstaticvoidMarkAsStarted(stringsectionId){if(string.IsNullOrWhiteSpace(sectionId))return;lock(_lock){if(_startedSections.Add(sectionId)){m_Log.Info($EAP 啟動標(biāo)記成功 section:{sectionId});SaveToFile();// 立即持久化}else{m_Log.Warn($EAP 重復(fù)啟動嘗試 section:{sectionId});}}}publicstaticvoidMarkAsEnded(stringsectionId){if(string.IsNullOrWhiteSpace(sectionId))return;lock(_lock){if(_startedSections.Remove(sectionId)){m_Log.Info($EAP 結(jié)束標(biāo)記成功 section:{sectionId});SaveToFile();// 立即持久化}}}publicstaticboolIsStarted(stringsectionId){if(string.IsNullOrWhiteSpace(sectionId))returnfalse;lock(_lock){return_startedSections.Contains(sectionId);}}/// summary/// 系統(tǒng)初始化/產(chǎn)線重啟時(shí)強(qiáng)制清零可選調(diào)用/// /summarypublicstaticvoidClearAll(boolsaveImmediatelytrue){lock(_lock){_startedSections.Clear();if(saveImmediately)SaveToFile();}m_Log.Warn(EAP所有section狀態(tài)已被清零);}}3.3 使用方式無需改動原有START/Ending邏輯啟動時(shí)自動加載。每次MarkAsStarted/MarkAsEnded自動保存。程序退出時(shí)AppDomain.ProcessExit 或 手動調(diào)用可額外調(diào)用一次SaveToFile()。4. 進(jìn)階建議定時(shí)備份每天備份一次JSON文件到Backup\目錄。版本控制JSON中增加Version: 1字段未來升級結(jié)構(gòu)時(shí)自動遷移。SQLite替代方案如果section數(shù)量1000或需要查詢歷史使用Microsoft.Data.Sqlite單文件數(shù)據(jù)庫建表EapStates (SectionId TEXT PRIMARY KEY, IsStarted INTEGER, UpdatedAt DATETIME)。優(yōu)點(diǎn)事務(wù)保證一致性。監(jiān)控與告警文件保存失敗時(shí)觸發(fā)SystemNotifEvent。安全考慮產(chǎn)線重啟時(shí)是否調(diào)用ClearAll()建議增加配置開關(guān)RuntimeConfiguration.ResetEapStateOnStartup。5. 總結(jié)與實(shí)施路線立即實(shí)施采用JSON文件方案最優(yōu)性價(jià)比。測試驗(yàn)證模擬程序崩潰、重啟確認(rèn)狀態(tài)是否正確恢復(fù)。監(jiān)控指標(biāo)新增日志“EAP狀態(tài)恢復(fù)成功/失敗”便于運(yùn)維。未來擴(kuò)展若系統(tǒng)演進(jìn)到分布式可無縫切換到Redis 事件溯源。這種持久化策略既符合SECS/GEM非易失性要求又保持了代碼簡潔性和工業(yè)級可靠性。如果您需要SQLite完整實(shí)現(xiàn)、配置化路徑、或結(jié)合現(xiàn)有CIM/MES數(shù)據(jù)庫的方案請?zhí)峁└嘞到y(tǒng)架構(gòu)細(xì)節(jié)我可以繼續(xù)細(xì)化代碼。