百億級存儲+毫秒級寫入:TDengine 在得物 APP 的落地實踐

做者|Lynx
導語 :得物許多系統和場景都須要作流量的監控和防禦,一天就可以產生數億數據,寫入速度達到萬TPS,該數據量級沒法用傳統的關係型數據庫處理。在對比了InfluxDB, OpenTSDB, Cassandra等時序數據庫的性能後,最終選擇 TDengine。

背景

做爲一家互聯網電商公司,得物有許多系統和場景都須要作流量的監控和防禦,因此在深度定製化開源流控防禦組件Sentinel時咱們加入了許多功能,幫助提高各業務系統的流控防禦。
 
備註:Sentinel組件地址:
 
在開發過程當中,咱們發現開源版本的Sentinel不支持流控數據持久化,而咱們很是須要這樣的功能:咱們須要一款數據庫,它可以承載大量的流量監控數據,並能對數據進行存儲和高效查詢。
 
目前在生產環境中,咱們有數百個業務系統、數千臺服務器接入了Sentinel,如此產生的流控數據無疑很是龐大。那麼對於這個需求來講,選擇一款適合的數據庫無疑極爲重要,一個好的選擇就可以達到事半功倍的效果。
 

01 數據庫選型

首先粗略估算一下當前數據量的理論上限:
目前生產環境擁有數千Sentinel Resources,而Sentinel的監控數據時間粒度按照秒來統計,那麼一天理論上就可以產生 數億 的數據, 理論寫入數據的速度也將達到萬TPS ,並且業務還在快速發展,能夠預見的是數據量將會進一步爆炸,顯而易見這個數據量級是沒法使用傳統關係數據庫的。
由於內部有一些應用在使用TiDB,因此首先看了一下使用TiDB的可行性,但很快就放棄了,畢竟它做爲一款分佈式數據庫,瞄準的徹底不是監控數據這種時序特色很是強的場景。
 
排除以後咱們就將調研重心放在了時序數據庫上。
 
主流時序數據庫裏面都各有優缺點:
  • InfluxDB,多是應用範圍最廣的時序數據庫,場景也合適;但集羣功能須要商業版。
  • OpenTSDB,基於HBase,對於目前簡單的需求來講過重了。
  • Cassandra,從找到的幾份對比報告來看性能不太知足要求。
 
當準備繼續瞭解Clickhouse時,被同事安利了一款 國產物聯網大數據平臺——TDengine
 
簡單在網上了解了一下,發現風評不錯,社區活躍度也高,後來就到官網上查閱了TDengine和其它數據庫的對比報告,發現從性能上看也很是優秀。
因而咱們就寫了一個demo,簡單使用了一下TDengine,整個過程當中,在清晰的文檔的幫助之下,學習成本尚可,因此咱們最終決定使用TDengine。
 

02 數據結構與建模方式數據結構

數據結構

 
首先咱們看一下Sentinel的流量數據是如何呈現的。
 
從上圖中能夠看出,左側是應用列表,在每一個應用的菜單中有獨立的監控面板,在監控面板中又以資源的粒度,統計了全部資源的流量數據,例如經過QPS、拒絕QPS、響應時間等。
因此從前端呈現的角度來看,數據的惟一鍵應當是應用-資源。
 
而後咱們在從內部實現角度看看數據的結構是怎麼樣的。
 
Sentinel客戶端在每臺服務器上統計了全部資源的流量數據,以秒的維度進行聚合,再記錄到本地日誌中。控制檯經過調用客戶端暴露的接口,獲取採集的流量數據,再以服務的維度,將全部單機的流量數據進行聚合,存儲在內存中。
 
因此咱們須要存儲的數據便是以應用-資源爲惟一屬性落入數據庫中的。
 

數據建模

TDengine官方文檔中建議的數據建模方式以下:
爲充分利用其數據的時序性和其餘數據特色,TDengine要求對 每一個數據採集點單獨建表 採用一個數據採集點一張表的方式,能最大程度的保證單個數據採集點的插入和查詢的性能是最優的。 在TDengine的設計裏, 表用來表明一個具體的數據採集點,超級表用來表明一組相同類型的數據採集點集合 。當爲某個具體數據採集點建立表時,用戶使用超級表的定義作模板,同時指定該具體採集點(表)的標籤值。與傳統的關係型數據庫相比,表(一個數據採集點)是帶有靜態標籤的,並且這些標籤能夠過後增長、刪除、修改。 一張超級表包含有多張表,這些表具備相同的時序數據schema,但帶有不一樣的標籤值。
 
能夠看到官方文檔中建議的數據建模方式徹底契合本場景的數據特色:一個應用-資源即爲一張表,全部的應用-資源放在一張超級表中以便作聚合查詢。因此在表結構的設計上,就使用了官方文檔推薦的這種方式。
 
另外,在標籤的選擇上,雖然目前尚未聚合操做的需求,可是考慮到將來的聚合操做很是可能會以應用的維度來作,因此咱們決定將一些應用的信息做爲標籤記錄在表中。
 

03 總體架構

 
目前的總體架構圖如上,各個接入了Sentinel的業務系統會向控制檯定時發送心跳請求維持本機器的健康狀態。
 
而控制檯會定時輪詢全部機器,拉取Sentinel客戶端在業務系統中記錄的監控數據,通過聚合處理後再向TDengine集羣批量寫入。
因爲場景簡單且並不是做爲主要的監控系統,且數據目前是能夠接受少許丟失,故沒有設計過多的失敗處理機制。
 

04 技術選型

Connector

在Connector選擇上,公司的主要開發語言就是Java,相關生態也都更完善,因此很天然地選擇了 JDBC形式的Connector
 
另外JDBC的性能相較於HTTP方式也會更好一些,同時JDBC驅動還 支持節點不可用時自動切換節點
 
惟一不方便的是JDBC的方式則會 強依賴本地庫函數 ,須要在客戶端的機器上也安裝TDengine,這樣在項目部署階段會稍微麻煩一些,不過總體來講是利大於弊的。
最近,官方更新了JDBC-RESTful的方式, 支持了跨平臺功能。 因爲公司服務器的操做系統都是Linux,沒有跨平臺的需求,因此仍是繼續使用JDBC-JNI的Connector。
 

數據庫鏈接池與ORM

數據庫鏈接池及ORM框架也選擇了在公司內部主流的 Druid+Mybatis ,根據官網的Demo代碼也能高效地完成接入。不過在Mybatis的使用上,只是在查詢時使用了Mybatis,將ResultSet轉爲更方便處理的實體,而寫入數據時則沒有使用Mybatis,爲了方便因此直接在內存中拼接好sql後進行執行。
 
整體來講,TDengine在適配主流框架方面很友好了,支持了 HikariCP、Druid、Spring JdbcTemplate、Mybatis 等,而且根據官網提供的Demo可以很快地實現接入,節省了大量時間,一些注意事項文檔中都有清晰列出。
 

集羣搭建

目前TDengine集羣有三個物理節點,均爲16核/64G內存/1T存儲。
官方的集羣搭建文檔寫的仍是很是詳盡的,直接按照文檔進行傻瓜式操做就能夠搭建起TDengine集羣。
 

建庫

在前期調研時發現,假定集羣只有三臺機器,若是數據量過大,副本數爲3,至關於在每臺機器上都存儲了一份完整數據,以可能的數據量推測,存儲和內存的壓力都會較大,因此在建庫時副本數選擇設置爲1。後續若集羣規模擴大,TDengine也支持動態修改副本數,能夠很輕鬆地完成到高可用集羣的切換。
 
另外考慮到查詢性能,將 blocks 設置爲16, cache 設置爲64MB。
CREATE DATABASE sentinel KEEP 365 DAYS 1 blocks 16 cache 64;

 

05 性能表現

目前TDengine承載了數百億數據,在生產環境運行平穩,CPU使用率平常不到1%,內存使用率穩定在25%如下。
 
下圖爲集羣中的一臺機器的監控圖表:
在使用早期TDengine版本(2.0.7.0)作調研時,內存方面是存在一些缺陷的,可是隨着版本迭代,目前看內存問題已經獲得了較好的解決。
 

寫入性能

控制檯的機器配置爲4核16G,批量寫入線程池設置的最大核心線程數爲16,數據庫鏈接池的最大線程數爲20,實際使用約14。
 
寫入流程以下:
 
對批量寫入設置的最大寫入條數爲400,寫入耗時以下:
 
能夠看到,大批量的寫入,耗時基本也能保持在10ms,屬於比較理想的範圍。目前尚未調整SQL語句最大長度,後續可能經過增長SQL語句長度的方式進一步優化寫入性能。
 

查詢性能

如下的耗時均未包含網絡開銷等,數據來自在客戶端上進行指定SQL語句的查詢。查詢的超級表數據量級在百億,如下給出了幾個典型場景的耗時狀況:
  • last_row函數:8.6ms 8.8ms 5.6ms
select last_row(*) from stable;

 

  • 查詢單個應用+資源某五分鐘的全部數據:3.4ms 3.3ms 3.3ms
select * from table where ts >= '2021-01-01 19:00:00' and ts < '2021-01-01 19:05:00';

 

  • 查詢單個應用+資源某3小時內每2分鐘的平均經過qps:1.4ms 1.3ms 1.4ms
select avg(pass_qps) from table where ts >= '2021-01-01 19:00:00' and ts < '2021-01-01 22:00:00' interval (2m);

 

  • 以服務維度分組查詢一天內每兩分鐘的平均經過qps:2.34s 2.34s 2.35s
select avg(pass_qps) from stable where ts >= '2021-01-01 00:00:00' and ts < '2021-01-02 00:00:00' interval (2m) group by appid;

值得一提的是,在2.0.7.0版本的TDengine中進行該查詢效率在十幾秒左右,當時認爲是不可接受的,通過幾個版本後優化效果顯著;前端

 
  • 以服務維度分組查詢三天內每一小時的平均經過qps:2.17s 2.16s 2.17s
select avg(pass_qps) from stable where ts >= '2021-01-01 00:00:00' and ts < '2021-01-03 00:00:00' interval (60m) group by appid;

不論是在大數據量範圍的聚合查詢,仍是指定查詢某一小區間內的所有數據,查詢效率仍是很是優異的。git

而且與前期調研時的數據相比,新版本的查詢性能優化了很是多,相信在將來的版本迭代中,會更進一步。github

 

存儲容量

目前Sentinel的數據沒有使用副本,全量數據分散在三臺機器中,根據計算得知TDengine對於Sentinel監控數據的壓縮率達10%,至關可觀。
 

06 總結

目前TDengine在得物暫時只是做爲一款時序數據庫小範圍試點來使用,沒有用到如流計算、內置查詢函數等一些高級功能,其做爲時序數據庫的讀寫性能、存儲表現都是使人滿意的。 除此以外在運維難度和學習成本上也是意想不到的低,很輕鬆就能搭好一套可用的集羣,這也是很是巨大的一個優點。另外TDengine的版本迭代很是快,一些在舊版本遇到的問題很快就獲得了修復,而且在性能優化方面效果也是十分顯著。
 
在調研、使用TDengine的這段時間,還有一個很重要的感覺就是官方文檔真的很是詳盡,技術部分的文章深刻淺出地講解了TDengine的技術架構、技術設計等,可以學習到很是多東西;指南類的文章則步驟清晰簡單,極大地下降了學習成本,讓開發人員可以很快地完成框架適配、集羣搭建、SQL編寫等。
 
後續咱們也會持續跟進TDengine的release notes,瞭解有哪些新featrue、優化點、bug fix等,在有必要的時候會進行版本升級。
 
期待TDengine的性能表現和穩定性可以不斷提高,將來也會在其餘合適的業務場景中做爲技術選型的備選項之一,例如將來可能不只須要存儲聚合後的數據,可能還須要存儲單機維度的流控數據。
 
注:本文數據基於2.0.7.0和2.0.12.1版本的TDengine。
相關文章
相關標籤/搜索