https://yq.aliyun.com/articles/54785
摘要: 本篇文章會詳細講解OpenTSDB的表結構設計,在理解它的表結構設計的同時,分析其采取該設計的深層次原因以及優缺點。它的表結構設計完全貼合HBase的存儲模型,而表格存儲(TableStore、原OTS)與HBase有類似的存儲模型,理解透OpenTSDB的表結構設計后,我們也能夠對這類數據庫的存儲
摘要
OpenTSDB是一個分布式的、可伸縮的時間序列數據庫,在DB-engines的時間序列數據庫排行榜上排名第五。它的特點是能夠提供最高毫秒級精度的時間序列數據存儲,能夠長久保存原始數據並且不失精度。它擁有很強的數據寫入能力,支持大並發的數據寫入,並且擁有可無限水平擴展的存儲容量。
它的強大的數據寫入能力與存儲能力得益於它底層依賴的HBase數據庫,也得益於它在表結構設計上做的大量的存儲優化。
本篇文章會詳細講解其表結構設計,在理解它的表結構設計的同時,分析其采取該設計的深層次原因以及優缺點。它的表結構設計完全貼合HBase的存儲模型,而表格存儲(TableStore、原OTS)與HBase有類似的存儲模型,理解透OpenTSDB的表結構設計后,我們也能夠對這類數據庫的存儲模型有一個更深的理解。
存儲模型
在解析OpenTSDB的表結構設計前,我們需要先對其底層的HBase的存儲模型有一個理解。
表分區
HBase會按Rowkey的范圍,將一張大表切成多個region,每個region會由一個region server加載並提供服務。Rowkey的切分與表格存儲的分區類似,一個良好設計的表,需要保證讀寫壓力能夠均勻的分散到表的各個region,這樣才能充分發揮分布式集群的能力。
存儲結構
OpenTSDB的基本概念
OpenTSDB定義每個時間序列數據需要包含以下屬性:
1. 指標名稱(metric name)
2. 時間戳(UNIX timestamp,毫秒或者秒精度)
3. 值(64位整數或者單精度浮點數)
4. 一組標簽(tags,用於描述數據屬性,至少包含一個或多個標簽,每個標簽由tagKey和tagValue組成,tagKey和tagValue均為字符串)
舉個例子,在監控場景中,我們可以這樣定義一個監控指標:
指標名稱:
sys.cpu.user 標簽: host = 10.101.168.111 cpu = 0 指標值: 0.5
指標名稱代表這個監控指標是對用戶態CPU的使用監控,引入了兩個標簽,分別標識該監控位於哪台機器的哪個核。
OpenTSDB支持的查詢場景為:指定指標名稱和時間范圍,給定一個或多個標簽名稱和標簽的值作為條件,查詢出所有的數據。
以上面那個例子舉例,我們可以查詢:
a. sys.cpu.user (host=*,cpu=*)(1465920000 <= timestamp < 1465923600):查詢凌晨0點到1點之間,所有機器的所有CPU核上的用戶態CPU消耗。
b. sys.cpu.user (host=10.101.168.111,cpu=*)(1465920000 <= timestamp < 1465923600):查詢凌晨0點到1點之間,某台機器的所有CPU核上的用戶態CPU消耗。
c. sys.cpu.user (host=10.101.168.111,cpu=0)(1465920000 <= timestamp < 1465923600):查詢凌晨0點到1點之間,某台機器的第0個CPU核上的用戶態CPU消耗。
OpenTSDB的存儲優化
了解了OpenTSDB的基本概念后,我們來嘗試設計一下表結構。
如上圖是一個簡單的表結構設計,rowkey采用metric name + timestamp + tags的組合,因為這幾個元素才能唯一確定一個指標值。
這張表已經能滿足我們的寫入和查詢的業務需求,但是OpenTSDB采用的表結構設計遠沒有這么簡單,我們接下來一項一項看它對表結構做的一些優化。
優化一:縮短row key
觀察這張表內存儲的數據,在rowkey的組成部分內,其實有很大一部分的重復數據,重復的指標名稱,重復的標簽。以上圖為例,如果每秒采集一次監控指標,cpu為2核,host規模為100台,則一天時間內sys.cpu.user這個監控指標就會產生17280000行數據,而這些行中,監控指標名稱均是重復的。如果能將這部分重復數據的長度盡可能的縮短,則能帶來非常大的存儲空間的節省。
OpenTSDB采用的策略是,為每個metric、tag key和tag value都分配一個UID,UID為固定長度三個字節。
上圖為優化后的存儲結構,可以看出,rowkey的長度大大的縮短了。rowkey的縮短,帶來了很多好處:
a. 節省存儲空間
b. 提高查詢效率:減少key匹配查找的時間
c. 提高傳輸效率:不光節省了從文件系統讀取的帶寬,也節省了數據返回占用的帶寬,提高了數據寫入和讀取的速度。
d. 緩解Java程序內存壓力:Java程序,GC是老大難的問題,能節省內存的地方盡量節省。原先用String存儲的metric name、tag key或tag value,現在均可以用3個字節的byte array替換,大大節省了內存占用。
優化二:減少Key-Value數
優化一是OpenTSDB做的最核心的一個優化,很直觀的可以看到存儲的數據量被大大的節省了。原理也很簡單,將長的變短。但是是否還可以進一步優化呢?
在上面的存儲模型章節中,我們了解到。HBase在底層存儲結構中,每一列都會以Key-Value的形式存儲,每一列都會包含一個rowkey。如果要進一步縮短存儲量,那就得想辦法減少Key-Value的個數。
OpenTSDB分了幾個步驟來減少Key-Value的個數:
1. 將多行合並為一行,多行單列變為單行多列。
2. 將多列合並為一列,單行多列變為單行單列。
多行單列合並為單行單列
OpenTSDB將同屬於一個時間周期內的具有相同TSUID(相同的metric name,以及相同的tags)的數據合並為一行存儲。OpenTSDB內默認的時間周期是一個小時,也就是說同屬於這一個小時的所有數據點,會合並到一行內存儲,如圖上所示。合並為一行后,該行的rowkey中的timestamp會指定為該小時的起始時間(所屬時間周期的base時間),而每一列的列名,則記錄真實數據點的時間戳與該時間周期起始時間(base)的差值。
這里列名采用差值而不是真實值也是一個有特殊考慮的設計,如存儲模型章節所述,列名也是會存在於每個Key-Value中,占用一定的存儲空間。如果是秒精度的時間戳,需要4個字節,如果是毫秒精度的時間戳,則需要8個字節。但是如果列名只存差值且時間周期為一個小時的話,則如果是秒精度,則差值取值范圍是0-3600,只需要2個字節;如果是毫秒精度,則差值取值范圍是0-360000,只需要4個字節;所以相比存真實時間戳,這個設計是能節省不少空間的。
單行多列合並為單行單列
多行合並為單行后,並不能真實的減少Key-Value個數,因為總的列數並沒有減少。所以要達到真實的節省存儲的目的,還需要將一行的列變少,才能真正的將Key-Value數變少。
OpenTSDB采取的做法是,會在后台定期的將一行的多列合並為一列,稱之為『compaction』,合並完之后效果如下。
同一行中的所有列被合並為一列,如果是秒精度的數據,則一行中的3600列會合並為1列,Key-Value數從3600個降低到只有1個。
優化三:並發寫優化
上面兩個優化主要是OpenTSDB對存儲的優化,存儲量下降以及Key-Value個數下降后,除了直觀的存儲量上的縮減,對讀和寫的效率都是有一定提升的。
時間序列數據的寫入,有一個不可規避的問題是寫熱點問題,當某一個metric下數據點很多時,則該metric很容易造成寫入熱點。OpenTSDB采取了和這篇文章中介紹的一樣的方法,允許將metric預分桶,可通過『tsd.storage.salt.buckets』配置項來配置。

總結