一、 引子javascript
在當前自動化、信息化、智能化的時代背景下,數據的做用日漸凸顯。而工業發展到現在,科技含量和自動化水平均顯著提升,但對數據的採集、利用纔開始起步。html
對工業企業而言,數據採集日益受到重視,主要應用場景包括:java
數據既然如此重要,對於SCADA不但必須有,並且高要求:git
要實現這些指標,很是具備挑戰性。github
2、 實時庫與歷史庫算法
工控環境特殊性在於,大量測點快速變化,需高速存取, IO密集型;數據結構簡單規則,無非就是名稱/ID、值、時間戳這些;數據流式存儲,只需在尾部插入,不刪不改。數據庫
所以常規的關係數據庫不只存取速度跟不上,也顯得殺雞用牛刀。實時庫和歷史庫就是爲工業環境準備的,測點的實時數據存儲在內存,保證最快的存取速度;數據超過必定範圍須要轉儲入歷史庫,我這裏用了自定義格式的二進制文件,力求數據單元空間佔用最小化、同時查詢速度最大化。數組
測點數據在內存中,包含【下位機映射緩存:ICache】→【快照數據集:TagList】→【歷史數據緩存:HistoryList】這樣的三級結構。緩存
ICache是下位機當前數據的緩存,隨掃描過程實時更新,繼承IReaderWriter接口,可讀可寫,能夠經過Tag的Read\Write讀取和更新。安全
對當前全部測點數據的快照查詢,能夠經過Tag的清單列表MetaTagList結合神器Linq實現。Linq對內存列表數據的查詢能力能夠說既強大又優雅,這是微軟送給C#碼農的禮物,再也不贅述。
測點數據改變就會生成一條新的記錄。這些記錄如立刻轉儲到數據庫或文件,則測點數量多變化快,其IO是系統不能承擔之重。但若是測點記錄堆積過多不及時清理,則一方面可靠性降低,如系統崩潰、斷電就會發生大量數據點丟失,同時內存佔用愈來愈大,影響系統性能。因此測點歷史數據的緩存容量應可根據測點數量和存取頻率自適應或由用戶自定義。
海量的測點數據,普通關係數據庫是難以招架的。如SQL SERVER免費版只有4-10個G上限。而這個容量可能一個月就溢出了。
所以,爲了適應天量數據,就須要二進制文件存儲。有人會問爲啥不用NO SQL,Hadoop這些高大上的東東,個人觀點是不追求高大上,由於工控數據不一樣於搜索引擎,都是簡單而標準的結構。能夠根據其特色進行有針對性的設計,無需部署複雜的NO SQL架構也同樣能夠實現高性能。
歷史數據庫要最大限度的壓縮數據,同時又要保證快速插入、快速查詢。
如何保證數據單元最小化?
分析存儲結構,一條記錄包括變量名、當前值、時間戳。
變量名可能爲一個長字符串,數據量大以後顯然是過於冗長。所以代之以ID號(2字節)。還能夠進一步壓縮,如相同變量存在一塊兒,ID也能夠省了。
當前值大部分是浮點數,4字節,這個不能縮減,不然影響數據精度。
時間戳爲DateTime,要佔8字節,但若是數據按日排序,日期部分省去,4個字節的時間部分就能夠精確到毫秒。
這樣,經過合理設計存儲結構,一條記錄能夠壓縮到8個字節(開關量5個字節)。
如何保證快速插入記錄?
存檔文件日積月累確定是愈來愈龐大,若是採用覆蓋式寫入或更新寫入,不只可靠性降低,讀寫成本也愈來愈高。想快速插入必須保證每次寫入不改變原來的數據,僅僅在末尾追加。
如何保證快速查詢?
首先爲保證可靠性和數據容量限制,數據分月存放。文件名爲【年-月.bin】。如需跨月查詢,按文件名搜索拼接便可。開頭256個字節存放日期索引。32*8字節,對應每日記錄的頭指針,也即上一日記錄的末尾。
主索引下,每一天的記錄頭爲日內索引區。包含一個索引數組,每一項索引有變量ID、變量長度、數量。如要查某日某ID的變量,便可先找到ID,再根據其變量長度*數量累加計算,便可定位到該變量的第一條記錄。
同一變量的記錄按時間戳順序排列。這樣,要定位到該變量某一時間的記錄,便可對時間戳採用二分法快速定位。
字符串類型的歸檔比較特殊,專門在EventLog做爲日誌存取。
3、 數據轉儲流程
數據在什麼狀況下會被採集?默認是變化了採集。若是一個數據長期不變,但須要按期採樣,能夠設置歸檔週期:
數據被採集以後首先是存在內存中。內存的特色是快,小。存取快,可是容量有限,採樣數據堆積多了就要清理轉儲到關係數據庫。
爲何要多一層關係數據庫?由於採樣的數據是時間序列的,但最終二進制文件的索引結構按照變量-時間戳排布,好比依次排入變量A-13:40,變量B-13:41,變量A-13:42這樣一個時間序列,如直接寫二進制存檔文件就需按變量排序並從新整理寫入,文件越大其寫入效率越低,對系統拖累越大。而先轉儲到關係數據庫,再按期將上一日的數據轉儲到二進制文件,既能夠充分利用關係數據庫的高性能批量插入功能(在SQL SERVER就是Sqlbulkcopy),又可利用關係庫的查詢排序能力,一箭雙鵰,轉儲以後數據庫記錄清空,也避免關係庫容量溢出的問題。
由上所述,轉儲包含【歷史數據緩存:HistoryList】→【關係數據庫:Log_HDA】→【二進制文件:bin文件】三級。
每轉儲成功一次,上一級的數據就清空,保證每一級之間的數據不重疊。
這樣一來,數據記錄就分佈於三個位置:內存、關係庫、二進制文件。要查詢數據,就須要對這三部分數據進行「拼接」。拼接的規則就是以當前級最末一條記錄的時間戳爲準。如當前級中沒有,就查下一級。
內存數據庫是一個HistoryData列表。有兩種狀況能夠觸發清理:按期清理、溢出清理。按期清理是設置固定的週期。溢出就是超過必定大小,到時間就轉到關係庫。可由server.xml配置。
就是數據庫的Log_HDA表。承上(內存)啓下(二進制文件),暫存數據。也包含ID、值、時間戳這幾個字段。天天凌晨開始,網關服務調用DataHelper內部的WriteToFile方法(實際是調用關係庫的WRITEHDATA存儲過程),對暫存的測點記錄按時間、變量排序,轉儲到二進制文件中。如寫入失敗,判斷最後一個時間戳,下一次繼續追加寫入,相似斷點續傳。
二進制存檔文件按月存放,自帶索引。全部對其操做均在DataHelper的HDAIOHelper 類中。包括從數據庫寫入、查詢、按期轉儲、壓縮歸檔(用旋轉門算法)等。爲提升讀寫性能,採用內存映射文件MemoryMappedFile。
4、 應用場景
數據的應用場景,主要是查詢、顯示、挖掘。查詢→生成各類報表、圖表,以供人工分析比較;顯示→圖形化展現,一目瞭然;挖掘→結合先進的挖掘工具,找出數據內在關聯性,提供決策支持。
目前支持的查詢場景包括:按時間段檢索、按變量ID檢索、獲取某變量在一段時間內的平均值/最大值/最小值/初始值/當前值。如要對一段記錄執行復雜查詢(如按時間間隔分組等),須要取出該時間段內全部記錄,用Linq查詢。
目前支持實時數據顯示和歷史數據趨勢圖。我這裏用了一套微軟俄羅斯研究院的DynamicDataDisplay開源組件,性能不錯,很適合動態圖顯示,目前還發展出了javascript版本。
利用微軟的RDLC報表和Chart圖表的強大功能,能夠方便的設計出各類複雜報表、圖表。順帶贊一下RDLC,集成於Visual Studio和SQL SERVER,能夠在Web顯示,支持內嵌表格、儀表、圖表、鑽取報表,還能夠方便的導出爲Excel、Pdf、Word,與.NET 完美集成,強烈推薦。
分佈式:對一個大系統,分佈式是必須的。即數據分別在不一樣節點採集、存儲,但形式上依然是一個總體,能夠統一查詢和傳輸。
內存映像:目前的測點緩存模式存在可靠性不足的問題(如忽然斷電或系統崩潰形成的數據丟失),可依賴Sqlite和內存映像解決。
MQTT:物聯網通行的MQTT協議能夠解決不一樣系統之間的實時訂閱傳輸問題。
查詢擴展:原生支持按時間間隔分組取出數據等經常使用查詢場景,能夠有效提升查詢性能。
安全控制:採用證書認證方式,增強權限管理,防止數據傳輸過程當中被篡改。
數據歸檔流程:
5、 下面的計劃
github地址:https://github.com/GavinYellow/SharpSCADA。QQ羣:102486275