物聯網的資料方案

血夜之末發表於2021-08-03

一、前言

經常可以在科幻電影/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,不存在一致性問題。那麼在設計資料解決方案時,側重點則會有所傾向。

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

相關文章