1、前言
從druid的0.11版本開始,我就開始關注它,每一次的版本的更新,druid都會使用戶體驗、性能更好,從以前手寫配置文件到可視化的界面操作,從實時節點進行任務提交到現在的索引服務等
流處理:
日志監控(Flume) ----> 消息中間件(kafka、MQ) ----> 流處理(spark streaming/Flink)
為了使指標開發更加簡單,我們一般不會直接采用Spark core或者Flink DataStream API進行計算,譬如很多時候都會通過樣例類的方式,將其轉化為table,然后利用SQL進行指標開發。
但是每一次的版本升級,我們如果要添加一些新的指標,那么只能更改原始代碼或者重新跑一個任務
並且有的時候,數據分析師往往需要的是自主查詢,自己定義指標、自己去開發指標!!
由於以上的困境,druid幫我們解決了很大一部分,當然它並非完美,在后面我會提到!
在此之前,我先拋出兩個概念:
OLTP:
對數據的增刪改查等操作,主要用於傳統的關系數據庫。
OLAP:
數據按不同維度的聚合,維度的下鑽,上卷等,主要用於數據倉庫。
2、druid
概念:
主要是解決低延遲下實時數據攝入與查詢的平台,本質是一個數據存儲,但是數據仍然是保存在(hdfs、文件系統等)中。
特點:
①列式存儲格式:
可以將列作為索引,為僅查看幾列的查詢提供了巨大的速度提升
②高可用、高並發:
①集群擴展、縮小、刪除、宕機都不會停止服務,全天候運行
②HA、sql的並行化執行、可擴展、容災等
③支持1000+的並發用戶,並提供隔離機制支持多租戶模式(多租戶就是並發互不影響)
④低延遲
Druid采用了列式存儲、倒排索引、位圖索引等關鍵技術,能夠在亞秒級別內完成海量數據的過濾、聚合以及多維分析等操作。
⑤存儲時候聚合:
無論是實時數據消費還是批量數據處理,Druid在基於DataSource結構存儲數據時即可選擇對任意的指標列進行聚合操作
聚合:提前做好sum,count等操作
3、druid架構
官網圖:
總體可以分為:四個節點 + 三個依賴
四個節點:
實時節點(Realtime Node):
實時攝入數據,對於舊的數據周期性的生成segment數據文件,上傳到deep storage中
為了避免單點故障,索引服務(Indexer)的主從架構已經逐漸替代了實時節點,所以現在的實時節點,其實里面包含了很多角色:
作用:可以通過索引服務的API,寫數據導入任務,用以新增、刪除、合並Segment等。是一個主從架構:
統治節點(overlord):
類似於Yarn ResourceManager : 負責集群資源的管理和分配
監視數據服務器上的MiddleManager進程,將提取任務分配給MiddleManager
中間管理者(middle manager):
類似於Yarn NodeManager : 負責單個節點資源的管理和分配
新數據提取到群集中的過程。他們負責從外部數據源讀取並發布新的段
苦工(peon):
類似於Yarn container :負責具體任務的執行
Peon進程是由MiddleManagers產生的任務執行引擎。
每個Peon運行一個單獨的JVM,並負責執行單個任務。
Peon總是與生成它們的MiddleManager在同一主機上運行
Router(路由:可選):
可在Druid代理,統治節點和協調器之前提供統一的API網關
注:統治節點和中間管理者的通信是通過zookeeper完成的
歷史節點(Historical Node):
加載已生成的segment數據文件,以供數據查詢
啟動或者受到協調節點通知的時候,通過druid_rules表去查找需要加載的數據,然后檢查自身的本地緩存中已存在的Segment數據文件,
然后從DeepStorage中下載其他不在本地的Segment數據文件,后加載到內存!!!再提供查詢。
查詢節點(Broker Node):
對外提供數據查詢服務,並同時從實時節點與歷史節點查詢數據,合並后返回給調用方
緩存:
外部:第三方的一些緩存系統
內部:在歷史節點或者查詢節點做緩存
協調節點(Coodinator Node):
負責歷史節點的數據負載均衡,以及通過規則(Rule)管理數據的生命周期
①通過從MySQL讀取元數據信息,來決定深度存儲上哪些數據段應該在那個歷史節點中被加載,
②通過ZK感知歷史節點,歷史節點增加,會自動分配相關的Segment,歷史節點刪除,會將原本在這台節點上的Segment分配給其他的歷史節點
注:Coordinator是定期運行的,並且運行間隔可以通過配置參數配置
三個依賴:
1) Mysql:
存儲關於Druid中的metadata,規則數據,配置數據等,
主要包含以下幾張表:
"druid_config”(通常是空的),
“druid_rules”(協作節點使用的一些規則信息,比如哪個segment從哪個node去load)
“druid_segments”(存儲 每個segment的metadata信息);
2 )Deep storage:
存儲segments,Druid目前已經支持本地磁盤,NFS掛載磁盤,HDFS,S3等。
3) ZooKeeper:
①查詢節點通過Zk來感知實時節點和歷史節點的存在,提供查詢服務。
②協調節點通過ZK感知歷史節點,實現負載均衡
③統治節點、協調節點的lead選舉
4、實時Segment數據文件的流動:
生成:
①實時節點(中間管理者)會周期性的將同一時間段生成的數據合並成一個Segment數據文件,並上傳到DeepStorage中。
②Segment數據文件的相關元數據信息保存到MetaStore中(如mysql,derby等)。
③協調節點定時(默認1分鍾)從MetaSotre中獲取到Segment數據文件的相關元信息后,將按配置的規則分配到符合條件的歷史節點中。
④協調節點會通知一個歷史節點去讀
⑤歷史節點收到協調節點的通知后,會從DeepStorage中拉取該Segment數據文件到本地磁盤,並通過zookeeper向集群聲明可以提供查詢了。
⑥實時節點會丟棄該Segment數據文件,並通過zookeeper向集群聲明不在提供該Sgment的查詢服務。 //其實第四步已經可以提供查詢服務了
⑦而對於全局數據來說,查詢節點(Broker Node)會同時從實時節點與歷史節點分別查詢,對結果整合后返回用戶。
查詢:
查詢首先進入Broker,按照時間進行查詢划分
確定哪些歷史記錄和 MiddleManager正在為這些段提供服務
Historical / MiddleManager進程將接受查詢,對其進行處理並返回結果
5、DataSource
Druid中的數據存儲在被稱為datasource中,類似RDMS中的table!!!
每個datasource按照時間划分。每個時間范圍稱為一個chunk(一般都是以天分區,則一個chunk為一天)!!! //也可以按其他屬性划分
在chunk中數據被分為一個或多個segment,每個segment都是一個單獨的文件,通常包含幾百萬行數據
注:這些segment是按照時間組織成的chunk,所以在按照時間查詢數據時,效率非常高。
數據分區:
任何分布式存儲/計算系統,都需要對數據進行合理的分區,從而實現存儲和計算的均衡,以及數據並行化。
而Druid本身處理的是事件數據,每條數據都會帶有一個時間戳,所以很自然的就可以使用時間進行分區。
為什么一個chunk中的數據包含多個segment!!!????原因就是二級分區
二級分區:
很可能每個chunk的數據量是不均衡的,而Duid為了解決這種問題,提供了“二級分區”,每一個二級分區稱為一個Shard(分片)
其實chunk、datasource都是抽象的,實際的就是每個分區就是一個Shard,每個Shard只包含一個Segment!!!,因為Segment是Shard持久化的結果
Druid目前支持兩種Shard策略:
Hash(基於維值的Hash)
Range(基於某個維度的取值范圍)
譬如:
2000-01-01,2000-01-02中的每一個分區都是一個Shard
2000-01-02的數據量比較多,所以有兩個Shard,分為partition0、partition1。每個分區都是一個Shard
Shard經過持久化之后就稱為了Segment,Segment是數據存儲、復制、均衡(Historical的負載均衡)和計算的基本單元了。
Segment具有不可變性,一個Segment一旦創建完成后(MiddleManager節點發布后)就無法被修改,
只能通過生成一個新的Segment來代替舊版本的Segment。
Segment內部存儲結構:
Segment內部采用列式存儲 //並不是說每列都是一個獨立的文件,而是說每列有獨立的數據結構,所有列都會存儲在一個文件中
Segment中的數據類型主要分為三種:
時間戳
維度列
指標列
對於時間戳列和指標列,實際存儲是一個數組
對於維度列不會像指標列和時間戳這么簡單,因為它需要支持filter和group by:
所以Druid使用了字典編碼(Dictionary Encoding)和位圖索引(Bitmap Index)來存儲每個維度列。每個維度列需要三個數據結構:
1、需要一個字典數據結構,將維值(維度列值都會被認為是字符串類型)映射成一個整數ID。
2、使用上面的字典編碼,將該列所有維值放在一個列表中。
3、對於列中不同的值,使用bitmap數據結構標識哪些行包含這些值。 //位圖索引,這個需要記住
注:使用Bitmap位圖索引可以執行快速過濾操作(找到符合條件的行號,以減少讀取的數據量)
Druid針對維度列之所以使用這三個數據結構,是因為:
使用字典將字符串映射成整數ID,可以緊湊的表示結構2和結構3中的值。
使用Bitmap位圖索引可以執行快速過濾操作(找到符合條件的行號,以減少讀取的數據量),因為Bitmap可以快速執行AND和OR操作。
對於group by和TopN操作需要使用結構2中的列值列表
實例:
1. 使用字典將列值映射為整數
{
"Justin Bieher":0,
"ke$ha":1
}
2. 使用1中的編碼,將列值放到一個列表中
[0,0,1,1]
3. 使用bitmap來標識不同列值
value = 0: [1,1,0,0] //1代表該行含有該值,0標識不含有
value = 1: [0,0,1,1]
因為是一個稀疏矩陣,所以比較好壓縮!!
Druid而且運用了Roaring Bitmap能夠對壓縮后的位圖直接進行布爾運算,可以大大提高查詢效率和存儲效率(不需要解壓縮)
Segment命名:
如果一個Datasource下有幾百萬個Segment文件,我們又如何快速找出我們所需要的文件呢?答案就是通過文件名稱快速索引查找。
Segment的命名包含四部分:
數據源(Datasource)、時間間隔(包含開始時間和結束時間兩部分)、版本號和分區(Segment有分片的情況下才會有)。
eg:wikipedia_2015-09-12T00:00:00.000Z_2015-09-13T00:00:00.000Z_2019-09-09T10:06:02.498Z
wikipedia: Datasource名稱
開始時間: 2015-09-12T00:00:00.000Z //該Segment所存儲最早的數據,時間格式是ISO 8601
結束時間: 2015-09-13T00:00:00.000Z //該segment所存儲最晚的數據,時間格式是ISO 8601
版本號: 2019-09-09T10:06:02.498Z //此Segment的啟動時間,因為Druid支持批量覆蓋操作,
//當批量攝入與之前相同數據源、相同時間間隔數據時,數據就會被覆蓋,這時候版本號就會被更新
分片號: 從0開始,如果分區號為0,可以省略 //分區的表現其實就是分目錄
注:單機形式運行Druid,這樣Druid生成的Segment文件都在${DRUID_HOME}/var/druid/segments 目錄下
注:為了保證Druid的查詢效率,每個Segment文件的大小建議在300MB~700MB之間
注:版本號的意義:
在druid,如果您所做的只是追加數據,那么每個時間chunk只會有一個版本。
但是當您覆蓋數據時,因為druid通過首先加載新數據(但不允許查詢)來處理這個問題,一旦新數據全部加載,
切換所有新查詢以使用這些新數據。然后它在幾分鍾后掉落舊段!!!
存儲聚合:
無論是實時數據消費還是批量數據處理,Druid在基於DataSource機構存儲數據時即可選擇對任意的指標列進行聚合操作:
1、基於維度列:相同的維度列數據會進行聚合
2、基於時間段:某一時間段的所有行會進行聚合,時間段可以通過queryGranularity參數指定
聚合:提前做好sum,count等操作
Segment生命周期:
在元數據存儲中!每個Segment都會有一個used字段,標記該段是否能用於查詢
is_Published:
當Segment構建完畢,就將元數據存儲在元數據存儲區中,此Segment為發布狀態
is_available:
如果Segment當前可用於查詢(實時任務或歷史進程),則為true。
is_realtime:
如果是由實時任務產生的,那么會為true,但是一段時間之后,也會變為false
is_overshadowed:
標記該段是否已被其他段覆蓋!處於此狀態的段很快就會將其used標志自動設置為false。