物聯網的數據方案

一、前言

經常可以在科幻電影/CG中看到,某個指揮官,對着前面一個超大的數據大屏,指點江山。那個數據大屏,上面有着各項指標,以及彙總數據,通過各色各樣的圖形展示出來。
從產品角度,指標與彙總條目的確定,決定了該數據大屏的價值。當然也可以支持自定義指標管理等。這不是本次的重點。
從前端角度,如何利用最少的系統資源,將衆多數據渲染出各個圖表,則是重中之重。技術上,可以採用阿里AntV那一套等。這同樣不是本次的重點。
從後端角度,如何在滿足數據請求內容的前提下,保證數據請求的高性能(低延遲,大吞吐量、高頻次等),就是該場景下的要點。這纔是本次的重點。

二、背景

作爲物聯網公司,需要向多個電力公司,進行數據大屏展示。其中數據大屏需要展示電力公司上百颱風力發電機的狀態,以及各項監控指標。其中監控指標中包含傾斜值、震動波形、結構應力等。而其中震動屬於高頻採集,頻次都是上千Hz的。即使用於計算傾斜值的傾斜傳感器,最低也是10/s。而監控大屏的數據,要求保證實時性。產品的要求是最好實時同步,最慢的指標也要保證5s一次的刷新率。最坑爹的,各個監控指標都需要可以查看歷史數據,以及實時數據。

如果按照震動數據來看,一條震動數據在數據庫中的記錄約爲100字節,一個震動傳感器每秒1000條記錄,一臺風機有4個震動傳感器,一個電力公司當前最多有100臺左右風機。這樣一來,一家電力公司每秒有震動數據40W條記錄,約爲40M。震動是記錄數最多的傳感器,但不是帶寬佔用最大的類型,音視頻佔據的帶寬更多。

當然,這是沒有任何處理的情況,所以看起來非常糟糕,前端完全無法處理。光震動就得每秒渲染40W數據,前端開發會殺了你的。尤其在歷史數據的情況下,簡直無法想象。

別說這能不能渲染了。我都得懷疑數據庫和帶寬是否可以承受了。畢竟還有別的公司,別的場景,以及別的傳感器。

三、數據寫入

1.數據清洗

首先,將一些明顯不合規的數據,給清理掉。比如由於人爲觸碰導致的傾斜值跳動,人爲作業導致的震動波動等。這部分過濾比例是很低的,主要是爲了降噪。

2.邊緣計算

風機的某項指標,往往是由多個傳感器計算得到的。比如傾斜值,往往是通過一組(至少三個)傳感器,計算得到的。
所以,我們先需要在風機的邊緣網關,進行協議解析,初始指標計算。
這部分的處理,往往會將多個數據,轉爲一個數據記錄。過濾效果還是不錯的。

PS:邊緣計算,還應用在邊緣網關的即時報警,以及機器關閉等操作。

3.採樣上傳

傳感器的原始數據指標,是非常多的,一不留神就把硬盤打爆了,甚至是帶寬。
而其實往往我們並不需要這麼多的原始數據指標。畢竟如果風力發電機要倒塌,也不會前一秒很正常,後一秒突然倒塌了。
所以,我們需要對原始數據指標進行採樣上傳。並且需要對傳感器採集頻率進行確定。
經過和業務、算法多方溝通&協商後,傾斜傳感器的採樣率定在了5%,應力傳感器的採樣率定在1%等。

那麼,傳感器的採集頻率和數據上傳的採樣率,有什麼區別&聯繫呢?
前者表示數據採集了,而且可能落盤了。後者表示對採集到的數據,抽取一部分,上傳到物聯網平臺。
那麼,爲什麼不直接降低採集頻率呢?
一方面,部分傳感器的採集頻率是有其上下限的,不一定滿足上傳需求。另一方面,合理頻率的原始數據,便於在發現問題後,進一步確定問題。
所以,我們當時的處理,是原始採集數據直接本地磁盤順序保存。並對原始數據進行採樣,進行本地數據庫保存,以及上傳。

4.特徵值提取

在某些垂直場景,我們需要計算出某些指標的特徵值(平均值、方差),經過算法的計算,得出有關目標實體的結論。
比如根據十分鐘內的傾斜值平均值和方差,我就可以知道當前風機傾斜狀況,並且通過方差,可以確定傾斜數據的穩定性。

5.分級採集

上面這一系列操作下來,數據已經過濾了七七八八。那麼還有沒有節省資源(功耗、存儲、帶寬等)的辦法呢?
正如上面提到的,風機就算要倒塌,也不會是一下子就倒塌了。所謂冰凍三尺,非一日之寒。咳咳,扯遠了。也就是說,我們日常採集的監控數據大多數是無效的。所以爲了提高資源利用率。在經過與業務、算法的溝通後,我這邊提出採集等級概念。即風機所屬傳感器平時只保持低頻採集狀態,只有指標出現可疑情況,纔會進入全功率狀態。
比如,震動傳感器,由於無法降頻與連續採樣(因爲震動的信息隱藏在連續的高頻數據中),故其採集成本最高,所以設定爲每天隨機時間段連續採集10分鐘。傾斜傳感器每小時採集1分鐘數據。當檢測到可疑情況,如傾斜值超出目標閾值,則全功率狀態。直到連續監測1小時,未出現可疑狀況,則重新迴歸低頻狀態。

其實,上述只有低頻採集和全功率狀態兩個採集狀態。其實可以擴展出多個採集級別。另外,還可以深入細化狀態轉換的觸發條件等。

通過分級採集,可以大大降低系統資源的浪費,卻又保證了系統目標。何樂而不爲呢。

四、數據存儲

數據存儲方面,只要關注物聯網數據的特性:量大、有序、越是最近的,訪問頻率越高。方案上,多多考慮數據異構即可。

1.存儲方式

簡單來說,雲平臺的一條指標記錄,會出現在四類存儲上:緩存、數據庫、統計表、歸檔表

a.緩存

由於剛剛插入的數據,經常被監控大屏、網頁等顯示終端展示,所以訪問頻次還是挺高的。尤其各類算法,經常需要掃描這些剛進來的萌新數據。
首當其衝的就是緩存key的設計。我們當時的設計就是綁定關係(公司-場景-目標-指標)+時間戳。這樣的查詢,還是相當快捷的。
其次就是失效時間的設定。我們當時的設定是1小時-1天。決定的標準是對應指標的訪問頻次和所佔存儲空間。
最後就是一致性問題,由於這些數據都是不變數據,所以不存在一致性問題。

不過緩存這邊需要注意一點,就是需要將指標緩存與其他業務數據隔離。我們當時的設計,是放在不同的Redis集羣。
至於集羣、可用性、持久化的問題,這裏就不展開了。

b.數據庫

數據庫一方面是爲了進行指標記錄的持久化,另一方面是爲了一天之前的數據查詢。
這部分其實真的沒什麼說的。大家對mysql都是很熟悉的。
這部分會涉及字段設計、索引設計(尤其是聯合索引和覆蓋索引)、分庫分表(路由規則設計)。雖然一般分庫分表前都會有主從同步,但在我們的場景下,還真沒太大必要,畢竟寫多讀少。而大多數讀的壓力,又放到了緩存上。我們的主從同步,也是爲了服務分庫分表,提高可用性。

字段設計

字段設計方面,一方面儘可能按照範式拆分。這不是電商場景,需要儘可能大寬表,這兩個場景完全不同。比如,記錄裏面可能是id、創建時間、公司、場景、目標、指標、指標值。但完全可以id、創建時間、指標id、指標值,再加一張指標綁定表。這樣可以節省非常多存儲,也可以大幅提高查詢性能(因爲一個數據頁可以容納更多記錄,從而降低整體IO成本)。
另外,真的需要需要在記錄中保存類似公司這樣的字段,最好轉化爲公司編碼,進行保存。

c.統計表

我們在數據查詢時,常會查看過去一個月、過去一年等數據,進而觀察數據趨勢。
而這個數據是不可能在每次查看時,從數據庫中拉取的。那服務器會崩潰的。
所以,會在每天凌晨構建天、周、月、年這樣的統計表,甚至可以結合其他維度各搜索條件,生成多個搜索結果。
具體實現,有三種途徑:

  1. 應用程序拉取備庫數據,進行統計,並將結果寫入統計表中。
  2. 備用數據庫通過計劃任務,定時觸發執行統計,並將結果寫入統計表。
  3. 利用大數據技術,如ODPS等MapReduce技術,定時拉取&統計數據,將結果寫入統計表。

d.歸檔表

超出一年多數據,查詢量很低。即使有查詢,往往也是類似平均值這樣的聚合數據統計。但隨着時間的推進,超出一年的數據,往往會越來越多。所以有的地方就直接拒絕這樣數據的詳細查詢。而另外一種方案,就是將其放在一個歸檔數據庫,不必性能很好,只要可以查詢即可。
當時我們的方案,就是將數據以一年爲單位放在一個新的數據庫中。即每到新的一年,則將往年的數據寫入到一個新的數據庫中,作爲歸檔表。而實時數據庫則最多隻保留最新十三個月的數據。

當然,歸檔表的數據也可以放在HBase這樣的Bigtable中,尤其在達到一定體量後。其rowKey的kv獲取,以及rowKey的scan獲取都符合歸檔表的需求。至於HBase的全表掃描,就算了。。。

五、數據查詢

其實數據存儲部分,已經提到了很多數據查詢的思路。
這裏按照兩個查詢維度的視角,進行分析。

1.詳細數據查詢

詳細數據查詢,則是直接查詢數據記錄,而不是統計數據。

a.短期數據

短期數據,如一天內的數據,可以直接從緩存中獲取。
當然也可以按照實際情況,將部分類型數據的失效時間,調整一下。

b.中期數據

中期數據,如一年內的數據,可以直接從數據庫中獲取。
同樣,中期的數據,可以按照實際情況,調整爲半年等。

c.長期數據

長期數據,如一年外的數據,可以從歸檔數據庫中獲取(實現基礎,可以是獨立的冷數據Mysql實例)。

d.小結

詳細數據查詢,必須確保各個range範圍的數據,都可以得到有效處理。
如果一個數據查詢範圍爲最近半年到最近一年半的數據,怎麼辦呢?一方面可以直接從歸檔數據庫查詢。另一方面可以進行查詢範圍的分解,如將上述範圍分解爲最近半年到最近一年(數據庫),以及最近一年到最近一年半(歸檔數據庫)。前者實現簡單,後者用戶體驗會更好一些,具體需要根據業務需求來進行確定。

2.統計數據查詢

這裏,我給出當時我們多個方案,以及優缺點。

a.方案一

方案:應用服務器直接拉取目標範圍的數據,然後對其中的數據,進行採樣&平均。
例子:目標範圍10W數據,就應用服務器直接拉取10W數據,然後採樣其中1W數據,然後每十個數據平均一下,最終得到1k數據,交給前端。
優點:實現簡單
缺點:隨着目標數量的上升,查詢效率線性下降。也就是目標數量上升一個數量級,查詢時間就上升一個數量級。
PS:在日常生活&工作中,不可避免負面影響增長,需要杜絕指數、避免線性、追求對數。尤其技術中,很多都可以將線性轉爲對數。比如流程抽象等。

b.方案二

方案:應用服務器,獲取開始時間的數據min_id,以及結束時間的數據max_id,進而獲得count = max_id - min_id,以及步長pace = count / 100(100表示返回給前端的數據條數)。通過min_id和pace,計算目標數據的id集,進而通過mysql查詢結果集。
例子:根據目標範圍的開始時間,查詢到min_id = 2000000,max_id = 3000000。進而獲得count = 1000000,以及pace = 10000。所以目標id集爲:2000000,2010000,2020000,2030000 ... ... 2990000。進而獲得mysql中對應結果集。
優點:實現並不複雜,只涉及代碼編寫。並且不會隨着數據量的增長,而查詢性能下降。
缺點:無法添加時間以外的查詢條件;無法處理非連續採集的數據(比如七天的時間範圍,有六天是不採集的。但這樣的方案,是無法滿足條件的)

c.方案三

方案:方案三是在方案二的基礎上,添加了對其他條件的過濾(有些類似mysql二級索引結果,回表驗證其他條件)。
例子:根據目標範圍的開始時間,查詢到min_id = 2000000,max_id = 3000000。進而獲得count = 1000000,以及pace = 10000。所以目標id集爲:2000000,2010000,2020000,2030000 ... ... 2990000。進而獲得mysql中對應結果集。然後再針對結果集,進行其他條件的過濾
優點:實現並不複雜,只涉及代碼編寫。並且不會隨着數據量的增長,而查詢性能下降。可以添加時間以外的查詢條件
缺點:查詢結果數量無法確定,存在返回結果數爲0的可能(這個可以二次查找,調整min_id,拼概率);無法處理非連續採集的數據(比如七天的時間範圍,有六天是不採集的。但這樣的方案,是無法滿足條件的)

d.方案四

方案:方案四是在方案二的基礎上,將id改爲了時間範圍。
根據目標範圍的開始時間min_time與結束時間max_time,獲得時間差time_range = max_time - min_time,進而獲得時間步長time_pace = time_range/100(100表示返回給前端的數據條數)。通過min_time和time_pace,獲得目標時間範圍集:min_time ~ min_time + time_pace、min_time + time_pace ~ min_time + time_pace * 2 ...
例子:略
優點:實現並不複雜,只涉及代碼編寫。並且不會隨着數據量的增長,而查詢性能下降。
缺點:無法添加時間以外的查詢條件;性能較低

e.方案五

方案:建立統計表,每天凌晨,都會計算前一天各指標數據的,多個搜索條件下的平均值等特徵值。便於後續進行“過去一個月”、“過去一年”等時間範圍的數據查詢。
優點:針對常見查詢,可以快速返回結果。
缺點:部分查詢條件無法進行統計,導致無法查詢。統計表的計算,是存在較大資源損耗的。並且由於統計表的時長特性,可能導致展現層數據點數量不統一(有的時候數據點很多,有的時候數據點非常稀疏。但可以通過自動統數據維度升降擊斃,進行較大的優化)。

六、總結

總結一下,數據架構的核心就是冷熱隔離、分級處理。
在設計數據架構前,需要確認數據情況,如物聯網場景寫多讀少,並且寫入數據都是Insert,不存在一致性問題。那麼在設計數據解決方案時,側重點則會有所傾向。

其實該方案和業務、應用架構等場景下都是一致的。大家都聽過什麼管理槓桿率、二八定律、好鋼要使在刀刃上這些語句。其實總結起來就一句話:
按照投入產出比,進行資源分配,從而在有限的資源下,追求獲得最高整體產出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章