TSDB時序數據庫用於存儲時間相關的數據,經常使用於監控系統的數據存儲,分佈式的TSDB提供了海量的數據存儲能力,如InfluxDB、OpenTSDB等。關於TSDB更多詳情,能夠參考該系列博客:時間序列數據的存儲和計算。
隨着監控數據量增加,對TSDB查詢性能的要求愈來愈高,尤爲是自動化運維繫統,大量的查詢操做讓基於磁盤的TSDB系統難以知足。
Facebook基於此需求推出了TSDB論文,設計了一個基於內存的TSDB:Gorilla。並開源了Gorilla的原型項目beringei。
本文主要基於Facebook官方論文和相關解讀分析Gorilla的架構和數據結構。git
Gorilla是一個內存TSDB。它在監控數據寫入HBase存儲以前,起到一個write-through cache的做用。Gorilla的數據模型是一個簡單的3元組,包括一個string類型的key、一個64-bit整型timestamp和一個雙精度浮點類型value。Gorilla使用一種新的timeseries壓縮算法,能夠按照時間順序將單條數據從16字節壓縮到平均1.37字節,縮小12倍。Gorilla的內存數據結構設計使得在保持對單個時間序列進行時間段查找的同時也能快速和高效的進行全數據掃描。
Gorilla無狀態的架構支持很是好的水平擴展能力。監控數據中定義的key用來惟一標識一個timeseries。根據key將timeseries數據進行shard(分片),每一個timeseries數據集會被映射到一臺單獨的Gorilla主機上。所以擴展Gorilla主機時,能夠經過調整分片算法將新的timeseries數據映射到新的主機上。
Gorilla支持高可用,經過將同一個timeseries數據寫到2個不一樣地域的主機中,來應對單節點故障、網絡切換、甚至是整個數據中心故障。在檢測到故障時,全部讀取操做會failed over到可用區域的主機,以確保用戶不會感知到任何中斷。github
Gorilla引入了對timestamp和value的高壓縮比算法,可大幅下降數據存儲的大小。Timestamp根據時間關聯的條目進行差值計算、以及差值的差值計算獲得佔用字節數很是小的數值並進行保存。一樣Value使用XOR算法進行計算獲得佔用存儲更小的數值進行保存。該算法在論文裏描述的很是清楚,此處再也不詳述。算法
Timeseries Map (TSmap)是Gorilla實現的主要數據結構。如上圖所示,TSmap包括一個C++ Vector和一個map(unordered_map)。Vector裏保存指向timeseries的指針(shared_ptr)。Map裏保存timeseries name(key)到timeseries指針(value)的映射,該map大小寫不敏感並保留原有大小寫。
Vector用於高效的分頁掃描全部數據(根據vector下標)。Map用於特定時間的timeseries查詢。該設計既知足快速按時間查詢的需求,又能提供高效的數據掃描。
C++的shared-pointers能夠在幾毫秒時間內掃描拷貝整個vector(或者幾個pages),能夠有效避免對新寫入數據流的影響。刪除的timeseries會將對應的vector設置爲「墓碑狀態」,即對應的內存不釋放,而是標記爲dead,並用於新的timeseries重複使用。
Map和vector的並行訪問使用一個簡單的讀寫spin lock進行保護。每一個timeseries上使用一個1-byte的spin lock實行互斥。單個timeseries相關不多的寫流量,所以讀寫的鎖爭用較少。數據庫
如上圖所示,還有一個ShardMap用於保存shardId(分片ID)到TSmap的映射。Timeseries在保存的時候根據timeseries name哈希散列到不一樣shardId(0 ~ NumberOfShard)。該map也是使用大小寫不敏感hash算法。系統中的Shard總數幾千之內,所以存儲空指針的額外開銷能夠忽略。一樣ShardMap也使用一個讀寫spin lock實現並行。
因爲數據根據shard進行了分區,單個map變得足夠小(大約100萬個條目),C++的unordered-map有足夠的性能,沒有鎖爭用的問題。緩存
最終保存的Timeseries數據結構(圖中TS)由一系列closed data blocks和一個open data block組成。每一個data block保存2小時的數據,Closed data block用於保存2小時之前的數據,Open data block用於保存2小時內最近的數據。Open data block是一個append-only的string,新的壓縮後的timestamps和values追加到該string後面。一旦Open data block寫滿2小時,則將其close。Closed data block不容許修改,直到將其從內存裏刪除。在closing過程當中,數據將被拷貝到從large slabs裏分配的內存以減小內存碎片。拷貝以後open data block能夠直接用於新的數據寫入,避免open data block常常改變size致使reallocate內存從而致使內存碎片。
根據時間範圍查詢時,將拷貝關聯的data blocks數據給遠程調用者。整個data block直接返回給Client,並由Client完成timestamp和value的解壓縮。網絡
通常TSDB的數據模型裏一般有一個Metric(如CPU使用率)和一些TAG(如host=10.10.10.10,cluster=A),並提供針對Metric和TAG的查詢、聚合等功能。而Gorilla每一個timeseries都有一個key string字符串做爲其惟一標識。論文中沒有太多描述,只是提了一下依賴更高層的工具去抽象和定義這個key string。簡單的實現可能就是MetricName+TAG1(key=value)+TAG2..組成一個string。數據結構
Gorilla使用GlusterFS存儲持久化數據,以應對單機故障。GlusterFS是一個兼容POSIX的3副本分佈式文件系統。之因此沒有選擇MySQL或者RocksDB,是由於Gorilla不須要提供一種數據庫查詢語言。
一個Gorilla主機上有多個shards的數據,每一個shard有一個單獨的目錄,目錄下包括4中類型的文件:Key list、append-only log、complete block files、checkpoint files。架構
綜上,Gorilla提供了一個對26小時內監控數據內存級分佈式水平擴展的TSDB,在long-term分佈式TSDB基礎上提供了短期(也是大部分查詢需求時間段)內秒級快速查詢的能力。app