小T導讀:SENSORO(北(bei)京升哲科技(ji)有(you)限公司)是(shi)一(yi)家領(ling)(ling)先的物聯網與人工智(zhi)能(neng)(neng)獨角獸企業。作為城(cheng)市級數(shu)(shu)據(ju)服務(wu)提供商(shang),公司在(zai)新一(yi)代信(xin)息技(ji)術領(ling)(ling)域擁有(you)核心(xin)研(yan)發能(neng)(neng)力(li),在(zai)國內首次(ci)實現物聯網與人工智(zhi)能(neng)(neng)領(ling)(ling)域端到端、一(yi)體化的技(ji)術與產品能(neng)(neng)力(li),包含自(zi)研(yan)物聯網通(tong)信(xin)芯片、通(tong)信(xin)基站(zhan)、智(zhi)能(neng)(neng)感知終端和智(zhi)能(neng)(neng)視覺終端及核心(xin)數(shu)(shu)據(ju)平臺等(deng)。SENSORO 面向城(cheng)市基礎設(she)施與核心(xin)要素提供全域數(shu)(shu)字化服務(wu)方案,通(tong)過(guo)將多項(xiang)核心(xin)自(zi)研(yan)關鍵性(xing)技(ji)術深入應用(yong)到智(zhi)慧城(cheng)市、鄉(xiang)村振興(xing)、區域治理、社會民生(sheng)等(deng)領(ling)(ling)域,打造物聯網與人工智(zhi)能(neng)(neng)應用(yong)的數(shu)(shu)字應用(yong)標(biao)桿(gan),賦能(neng)(neng)我國城(cheng)鄉(xiang)數(shu)(shu)字經濟的高質量發展(zhan)。
建立(li)城市級傳(chuan)感(gan)器網絡所涉及(ji)的傳(chuan)感(gan)器種(zhong)類十(shi)分多樣,由此產生(sheng)的數據(ju)量也十(shi)分龐大(da), 如果(guo)只是使用 MySQL、PostgreSQL 等 OLTP 系統進行數據(ju)的簡單存儲,不僅會(hui)產生(sheng)很多問題,而且(qie)其水平擴(kuo)展能(neng)力也有(you)限,同(tong)時也因為沒有(you)專門針對物聯網數據(ju)進行優化而缺乏足夠的壓(ya)縮(suo)效果(guo),數據(ju)存儲成本(ben)很高(gao)。
在系統開發初期,結合之前的經驗我們先是選擇了 Apache Druid 作為存儲傳感數據的數據庫,然而在使用過程中卻遇到了各種各樣的問題,這使得我們將目光轉移到了 TDengine 這款時序數據庫(Time-Series Database)。事實上,在 TDengine 開源之初我們就注意到了這個新興的時序數據庫,閱讀當時(shi)(shi)發布的(de)白(bai)皮書與性能(neng)測(ce)試(shi)(shi)報告時(shi)(shi)驚(jing)艷感由(you)衷而生(sheng),隨即聯(lian)絡到了濤思的(de)同學們(men),進(jin)行(xing)了更深入的(de)交流與測(ce)試(shi)(shi)。
但因為平臺涉及的特殊數據模型,合作便一直擱置了下來。主要問題在于數據有 A、B 兩個維度且是多對多關系還會隨時間變化,基于 A 創建子表(此時無法將 B 設置成 tag 列)就無法通過 B 進行聚合查詢,還需要花費較大的時間與精力改造成 TDengine 特有的超級表結構。之后 TDengine 也經過了多個版本迭代,支持了 join 查詢,而我們的數據模型也發生了變化,遷移到 TDengine 時不再需要做出很多的系統模塊改動。
一、基于 Apache Druid 現存系統的問題
基于 Apache Druid,系統最大的問題就(jiu)是維護成本了。Druid 劃分(fen)了 Coordinator、Overlord、Broker、Router、Historical、MiddleManager 六個進程,要實現完(wan)整的集群(qun)功能(neng)(neng),其還(huan)需要 Deep Storage (支(zhi)持 S3 和 HDFS),Metadata Storage(典型如 MySQL、PGSQL),以(yi)及(ji)為實現服務發現與(yu)選主功能(neng)(neng)而需要 的ZooKeeper,由此(ci)也(ye)可以(yi)看出 Druid 是一套極為復雜的系統。
同時,Druid 對外(wai)部的(de)各種依(yi)賴也(ye)導致運維同學在處理一(yi)些問題時,會直接(jie)或間(jian)接(jie)地影(ying)響(xiang)到它(ta)的(de)運行,比如我們將(jiang) S3 的(de) AccessKey 進(jin)行規范化處理——由以(yi)前的(de)全局通用改(gai)成某個 bucket 唯(wei)一(yi),或者將(jiang) PGPool 升級,都會影(ying)響(xiang)到 Druid。而且 Druid 針對每(mei)一(yi)個進(jin)程(cheng)和外(wai)部依(yi)賴都有厚厚的(de)幾頁(ye)配置(zhi)項,且從 JVM 自身來看(kan),不同進(jin)程(cheng)、配置(zhi)、MaxDirectMemorySize 都會嚴重(zhong)影(ying)響(xiang)寫入查詢性能(neng)。如果你要從官方文檔的(de)配置(zhi)頁(ye)面從頂劃到底(di),可能(neng)會把手指劃抽筋。

為了節(jie)省(sheng)存儲成(cheng)本,我們在(zai)部署(shu) Druid 集群時(shi)對(dui)于(yu) Historical 節(jie)點采用了多種(zhong)不(bu)(bu)同(tong)(tong)的(de)機器(qi)(qi)配置,在(zai)近(jin)期數(shu)據的(de)處(chu)理上,機器(qi)(qi)配備(bei) SSD 硬(ying)盤并(bing)設置較多副(fu)本數(shu)。這(zhe)導致數(shu)量最多的(de) Data Server節(jie)點,有一些(xie)不(bu)(bu)能與(yu) Middle Manager 共享(xiang),同(tong)(tong)時(shi)不(bu)(bu)同(tong)(tong)的(de)節(jie)點因為配備(bei)了不(bu)(bu)同(tong)(tong)核數(shu) CPU 與(yu)內存,對(dui)應的(de) JVM 配置和其(qi)他線程池(chi)配置也不(bu)(bu)同(tong)(tong),進(jin)一步加大(da)了運維成(cheng)本。
另外,由于(yu) Druid 的(de)(de)數(shu)據模型分(fen)為 Primary timestamp、Dimensions、Metrics,而(er) Metrics 列(lie)只(zhi)能(neng)在(zai)啟(qi)用(yong) Druid 的(de)(de) Rollup 時才會存在(zai),而(er) Rollup 意味(wei)著寫入時聚(ju)合且數(shu)據會有一定(ding)程(cheng)度的(de)(de)丟失。這(zhe)種情況下,想把每行數(shu)據都原原本本地記錄下來,只(zhi)能(neng)把數(shu)據全都記錄在(zai) Dimensions 列(lie),不(bu)使(shi)用(yong) Metrics,而(er)這(zhe)也(ye)會影響數(shu)據壓縮以及(ji)某(mou)些場景的(de)(de)聚(ju)合查詢(xun)性能(neng)。
此(ci)外(wai)還有(you)一(yi)些問(wen)(wen)題(ti)如(ru) Druid 的 SQL 編(bian)譯性能問(wen)(wen)題(ti)、原生查詢復雜的嵌(qian)套結構等在此(ci)便(bian)不再一(yi)一(yi)列舉,總之(zhi)基于上述問(wen)(wen)題(ti)我們決定再次詳細測試一(yi)下 TDengine Database。
二、與 Druid 的對比
導入相(xiang)同的(de)兩份(fen)數(shu)據(ju)到 Druid 和 TDengine 中(zhong),以(yi)下為在三(san)節點(dian)(8c16g)環境下,100 萬(wan)個傳(chuan)感設(she)(she)備、每個傳(chuan)感設(she)(she)備是 40 列(lie)(6 個字符(fu)串數(shu)據(ju)列(lie)、30 個 double 數(shu)據(ju)列(lie)以(yi)及 4 個字符(fu)串 tag 列(lie)),總計 5.5 億(yi)條記(ji)錄的(de)結果。這里要注意一(yi)點(dian),由于數(shu)據(ju)很(hen)多為隨(sui)機(ji)生成,數(shu)據(ju)壓(ya)縮率一(yi)般會比真實情況要差。
- 資源對比:

- 響應時間對比:
- 隨機單設備原始數據查詢
- 查詢結果集100條
- 重復1000次查詢,每次查詢設備隨機指定
- 查詢時間區間分別為:1天、7天、1月,
- 統計查詢耗時的最大值、最小值、平均值
- SELECT * FROM device_${random} LIMIT 100

- 隨機單設備聚合查詢
- 聚合計算某列的時間間隔的平均值
- 重復1000次查詢,每次查詢設備隨機指定
- 查詢時間區間分別為:1天、7天、7天、1月,對應聚合時間為1小時、1小時、7天,7天。
- 統計查詢耗時的最大值、最小值、平均值
- SELECT AVG(col_1) FROM device_${random} WHERE ts >= ${tStart} and ts < ${tEnd} INTERVAL(${timeslot})

- 隨機多設備聚合查詢
- 聚合計算某列的時間間隔的總和
- 重復1000次查詢,每次查詢設備約10000個
- 查詢時間區間分別為:1天、7天、7天、1月,對應聚合時間為1小時、1小時、7天,7天。
- 統計查詢耗時的最大值、最小值、平均值
- SELECT SUM(col_1) FROM stable WHERE ts >= ${tStart} and ts < ${tEnd} AND device_id in (${deviceId_array}) INTERVAL(${timeslot})

可以看到,TDengine 的空間占用只有 Druid 的 60%(沒有計算 Druid 使用的 Deep Storage)。針(zhen)(zhen)對單一設備的(de)查詢與(yu)聚和的(de)響(xiang)應時(shi)(shi)間比 Druid 有(you)倍數的(de)提升,尤其(qi)時(shi)(shi)間跨度(du)較久時(shi)(shi)差距更明(ming)顯(在十倍以(yi)上),同時(shi)(shi) Druid 的(de)響(xiang)應時(shi)(shi)間方差也較大(da)。然而針(zhen)(zhen)對多子(zi)表的(de)聚合操作,TDengine 與(yu) Druid 的(de)區別便不再(zai)明(ming)顯,可以(yi)說是各有(you)優劣。
總之,TDengine 與 Druid 在物聯網數據方面的對比,前者的性能、資源使用方面均有較大領先。再結合 TDengine 安裝部署配置上的便利性(我們會涉及到一些私有化應用的部署場(chang)景,這(zhe)點對我們來說非(fei)常重要),及相較于 Apache 社區(qu)其所提供(gong)的更可靠與及時的商業服務(wu),我們最終決定將傳(chuan)感數據遷移到 TDengine中(zhong)。
三、遷移后的系統
- 建表與遷移
因為我們系統內接入的設備種類非常多,所以一開始數據存儲便以大寬表的方式存儲:50列double類型、20列binary類型、10列bool以及額外的幾列通用列,同時還額外維護了一份記錄了每列實際列名的映射表。這種存儲模式在基于 Druid 的系統中便已經實現了,在 TDengine 中我們也創建了同樣結構的超級表,列名如: number_col1, number_col2, ..., number_col50, str_col1, str_col2, ..., str_col10。
在原本的數據寫入服務中,會將{"foo": 100, "bar": "foo"}轉換成 {"number_col1": 100, "str_col1": "foo"},同時記錄一份 `[foo=> number_col1, bar=>str_col1]` 的(de)(de)映射關系(xi)(每(mei)一型號的(de)(de)設備共用相同的(de)(de)映射),然后(hou)將處(chu)理后(hou)的(de)(de)數據寫入Kafka集群中(zhong)。
現在(zai)要(yao)將(jiang)數據(ju)(ju)寫入(ru)(ru)到 TDengine 中,也只需(xu)要(yao)基于原(yuan)本(ben)(ben)要(yao)寫入(ru)(ru) Kafka 的(de)數據(ju)(ju)來生成對應(ying)的(de) insert SQL,再(zai)通(tong)過 TAOSC 寫入(ru)(ru) TDengine 即可(ke),且在(zai)數據(ju)(ju)查詢時(shi)也會自動從映(ying)射關(guan)系中讀取對應(ying)的(de)真實列名返回給調用(yong)方。這樣對上層(ceng)應(ying)用(yong)來說,輸入(ru)(ru)輸出的(de)數據(ju)(ju)保證了統一且無需(xu)變(bian)動,同時(shi)即便我們系統頻繁的(de)增加新的(de)設(she)備(bei)類型,基本(ben)(ben)上也不再(zai)需(xu)要(yao)手動創(chuang)建新的(de)超(chao)級表。

當然期間也(ye)遇到(dao)了一(yi)些(xie)小問(wen)題,主要(yao)就是(shi)在根據設(she)備建表時(shi)(shi),某些(xie)前(qian)綴加設(she)備唯一(yi)標(biao)識構成(cheng)表名,但設(she)備唯一(yi)標(biao)識里面可(ke)能會包含減號(hao)”-“這種特(te)殊(shu)字(zi)符。對(dui)于當時(shi)(shi)的(de)(de) TDengine Database 版本(ben)來說,這種特(te)殊(shu)或保留字(zi)符是(shi)無(wu)法作為表名或列名的(de)(de),所以額外(wai)處(chu)理了一(yi)下(xia)。此外(wai)列名無(wu)法區分(fen)大小寫也(ye)使得(de)我們原本(ben)“fooBar”這種駝(tuo)峰方式的(de)(de)命名需要(yao)修改(gai)成(cheng)“foo_bar”這種下(xia)劃(hua)線分(fen)隔。不過(guo) TDengine 2.3.0.0 之后支(zhi)持了轉義(yi)字(zi)符“`”后,這些(xie)問(wen)題就都得(de)到(dao)了解決。
- 遷移后效果
遷移后,TDengine 為我(wo)們系(xi)統里的(de)(de)(de)各式各樣的(de)(de)(de)傳感器提供了統一的(de)(de)(de)數(shu)據存儲服務,通過中間數(shu)據層(ceng)的(de)(de)(de)封裝(zhuang),我(wo)們上層(ceng)的(de)(de)(de)業務基本無需(xu)修改(gai)便可以順利地遷移過來。相比于 Druid 需(xu)要(yao)(yao)部(bu)(bu)署(shu)各種各樣的(de)(de)(de) Server,TDengine 僅需(xu)要(yao)(yao)部(bu)(bu)署(shu) DNode 即可,也不再需(xu)要(yao)(yao)部(bu)(bu)署(shu) PG、ZK、Ceph 等外(wai)部(bu)(bu)依賴。
遷移后的應用接口響應時間 P99 也從 560 毫秒左右降低到 130 毫秒(涉及(ji)多次內部 RPC 調(diao)用與 Database 的查詢(xun)并不(bu)單純表示 TDengine 查詢(xun)響應時(shi)間):


對于開發人員來(lai)說,TDengine 讓我(wo)們不需(xu)要再花費太多(duo)時(shi)間與精力(li)去研究查詢怎(zen)么樣更高效(只要不直接使用數據列做(zuo)過濾條件并指(zhi)定合理的查詢時(shi)間段(duan)后,大(da)部分查詢都能得到滿意的響應時(shi)間),可以(yi)更多(duo)地(di)聚焦(jiao)于業(ye)務功能實現(xian)上。同時(shi)我(wo)們的運維同學(xue)們也得以(yi)從 Druid 的各個(ge)復雜模塊(kuai)中解(jie)脫出來(lai),在(zai)操作任何(he)中間件時(shi)都不需(xu)要再對 Druid 的情(qing)況進行確(que)認。
且值得一提的是,在實際業務環境中,以上面描述的方式創建多列的超級表,雖然會存在大量的空列,但得益于 TDengine 的優化,能達到恐怖的 0.01 的壓縮率,簡單計算下來大約需要 3.67GB 每億條。另外一張超級表(約 25 列數據列)針對傳感器數據進行單獨建模(不會存在空列的情況),壓縮率也有 0.2,計算一下空間使用約合 3.8GB 每億條。這(zhe)樣看來使用寬表(biao)這(zhe)種存儲(chu)方式結(jie)合(he) TDengine 的強大壓縮能力(li)也不會帶(dai)來很多額外(wai)的硬件成(cheng)本(ben)開銷,但(dan)卻能顯著(zhu)的降低我們的維護成(cheng)本(ben)。
四、未來規劃
目前我們基于 TDengine 主要還是存儲傳感器設備上傳的數據,后續也計劃將基于傳感數據分析出的事件數據遷移過來,甚至還打算將 AI 識別算法分析出的結構化數據也存儲到 TDengine 中。總之(zhi),經歷了此次合作(zuo),我(wo)們會(hui)把 TDengine 作(zuo)為數據中臺里重要(yao)的(de)(de)(de)一種存(cun)儲(chu)引擎使用(yong),而(er)非(fei)簡單地存(cun)儲(chu)傳感器數據。未(wei)來,相信在濤思同學們的(de)(de)(de)支持(chi)下,我(wo)們能(neng)為客戶提供(gong)更加優質的(de)(de)(de)服務,打造物聯網(wang)與人工智(zhi)能(neng)應用(yong)的(de)(de)(de)數字標桿。
作者簡介
段雪林,北京(jing)升哲高級后端開發工(gong)程師,主要負責升哲靈思(si)物聯網中臺(tai)的設計開發工(gong)作。


























