> Photo by Carlos Muza on Unsplash
Apache druid是最流行的在線分析處理(OLAP)開源解決方案之一。 Airbnb和Netflix等許多科技公司都使用它來(lái)對(duì)每分鐘包含數(shù)百萬(wàn)個(gè)事件的數(shù)據(jù)流進(jìn)行查詢。 它使公司可以近乎實(shí)時(shí)地做出決策。
Druid的主要賣點(diǎn)是它可以輕松擴(kuò)展到百萬(wàn)RPM的寫入速度,甚至超過(guò)亞秒級(jí)的延遲,并且在整個(gè)操作過(guò)程中都具有很高的可用性。 但是如今,許多數(shù)據(jù)庫(kù)都具有高可用性和亞秒級(jí)的延遲,那么,什么使druid與眾不同?
鍵/值存儲(chǔ) 和 Druid

> From druid's official documentation (http://druid.io/technology)
關(guān)系數(shù)據(jù)庫(kù)(例如MySQL)擅長(zhǎng)處理通常具有行級(jí)查詢的事務(wù)性工作負(fù)載。 對(duì)于分析,您需要獲取某些列的匯總,這需要掃描多個(gè)分片上的很多行。 RDBMS不能足夠高效地執(zhí)行實(shí)時(shí)數(shù)據(jù)探查。
就NoSQL鍵值數(shù)據(jù)庫(kù)而言,由于您需要查詢多個(gè)節(jié)點(diǎn)上的多個(gè)分區(qū),因此聚合計(jì)算肯定會(huì)效率低下。 您可以通過(guò)以某種粒度(例如1分鐘)預(yù)先計(jì)算數(shù)據(jù)并將其存儲(chǔ)在密鑰中來(lái)解決此問(wèn)題。 但是,通過(guò)遵循這種方法,您將失去在多個(gè)窗口上進(jìn)行瀏覽的能力。 為所有可能的列組合存儲(chǔ)聚集體也是不可行的,因?yàn)檫@會(huì)導(dǎo)致存儲(chǔ)需求呈指數(shù)增長(zhǎng)。
Druid旨在解決這些問(wèn)題,即能夠在提供低延遲和高可用性的同時(shí)探索實(shí)時(shí)數(shù)據(jù)和歷史數(shù)據(jù)。
它是如何工作的?
Druid由多個(gè)節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)扮演不同的角色。 這些所有節(jié)點(diǎn)相互協(xié)調(diào)工作(通常使用Apache Zookeeper進(jìn)行協(xié)調(diào))以提供性能。

> Druid Architecture from http://druid.io/docs/latest/design/
讓我們更詳細(xì)地討論每個(gè)節(jié)點(diǎn)。 如果您已經(jīng)熟悉節(jié)點(diǎn)及其交互,則可以直接跳到最后一部分。
實(shí)時(shí)(中層管理)
這些節(jié)點(diǎn)負(fù)責(zé)處理讀寫的實(shí)時(shí)數(shù)據(jù)。 這些文章特別包括四個(gè)主要步驟:
- · 攝取-將數(shù)據(jù)寫入Druid時(shí),它首先進(jìn)入該節(jié)點(diǎn)的內(nèi)存索引緩沖區(qū)。 該緩沖區(qū)基于堆,事件以行方式存儲(chǔ)。
- · 持久-為了避免堆溢出,該索引會(huì)定期保留在磁盤上以提高持久性。 內(nèi)存中的緩沖區(qū)將轉(zhuǎn)換為面向列的存儲(chǔ)格式,并使其不可變。 然后,將持久化的索引加載到堆外內(nèi)存中以進(jìn)行更快的查詢。
- · 合并-定期的后臺(tái)任務(wù)將不可變的塊合并為所謂的段。
- · 移交-段最終上傳到分布式數(shù)據(jù)存儲(chǔ)(稱為深度存儲(chǔ)),例如HDFS,以提高持久性和可用性。 它還會(huì)在MySQL中更新細(xì)分的元數(shù)據(jù),以供其他節(jié)點(diǎn)查看。

歷史的
這些節(jié)點(diǎn)從深度存儲(chǔ)加載段,然后在其之上提供查詢。 在Druid上運(yùn)行的大多數(shù)分析查詢大部分時(shí)間將進(jìn)入這些節(jié)點(diǎn)。 因此,這些節(jié)點(diǎn)是集群的主要工作者。
節(jié)點(diǎn)從Zookeeper獲得在深度存儲(chǔ)中發(fā)布的任何新段的信息。 然后下載并加載該細(xì)分受眾群以進(jìn)行投放。 節(jié)點(diǎn)還在本地磁盤中緩存了一些段,這使它們可以在發(fā)生某些重啟時(shí)快速為查詢提供服務(wù)。 節(jié)點(diǎn)還提供讀取一致性,因?yàn)樗鼈儍H處理不可變的段。
歷史節(jié)點(diǎn)也可以分為多個(gè)層次,每個(gè)層次具有不同的可配置性。 例如 可以將具有較高核心數(shù)的節(jié)點(diǎn)放在一個(gè)層中,以為經(jīng)常訪問(wèn)的數(shù)據(jù)提供服務(wù),而為其他數(shù)據(jù)提供資源較少的節(jié)點(diǎn)。
ZooKeeper的可用性阻礙了這些節(jié)點(diǎn)加載新段的能力,但是舊段繼續(xù)提供服務(wù)而沒有任何停機(jī)時(shí)間。

> Historical Nodes in action (from druid whitepaper)
經(jīng)紀(jì)人Broker
所有用戶查詢都轉(zhuǎn)到代理節(jié)點(diǎn)。 然后,這些節(jié)點(diǎn)將請(qǐng)求重定向到適當(dāng)?shù)臍v史和實(shí)時(shí)節(jié)點(diǎn),將結(jié)果合并并發(fā)送回去。 節(jié)點(diǎn)還維護(hù)內(nèi)存中的LRU緩存(可以更改為使用Memcached)。 緩存包含每個(gè)段的結(jié)果。 但是,僅歷史節(jié)點(diǎn)段的結(jié)果是緩存,因?yàn)閷?shí)時(shí)數(shù)據(jù)會(huì)經(jīng)常保留更改。

這些節(jié)點(diǎn)還使用Zookeeper發(fā)現(xiàn)其他節(jié)點(diǎn)。 如果Zookeeper發(fā)生故障,它們將以集群狀態(tài)的最后快照為數(shù)據(jù)提供服務(wù)。
協(xié)調(diào)員
由于"歷史"節(jié)點(diǎn)很笨,因此協(xié)調(diào)員有責(zé)任告訴他們?cè)撛趺醋觥?具體來(lái)說(shuō),協(xié)調(diào)器發(fā)出以下命令:
- · 將實(shí)時(shí)節(jié)點(diǎn)發(fā)布的新細(xì)分加載到HDFS中。
- · 刪除過(guò)時(shí)的數(shù)據(jù)。
- · 復(fù)制數(shù)據(jù)以實(shí)現(xiàn)冗余,以便您可以容忍節(jié)點(diǎn)故障。
- · 跨多個(gè)節(jié)點(diǎn)負(fù)載均衡數(shù)據(jù)。
只有一個(gè)協(xié)調(diào)器節(jié)點(diǎn)被選為領(lǐng)導(dǎo)者,它負(fù)責(zé)整個(gè)操作,而其余節(jié)點(diǎn)僅充當(dāng)備份者。
協(xié)調(diào)器從Zookeeper獲取最新的群集狀態(tài),以及有關(guān)應(yīng)從MySQL服務(wù)的段的信息。 MySQL的中斷以及Zookeeper的中斷阻礙了分配或刪除新段的能力,但是舊段仍然是可查詢的。
那么,什么使其比同行更好?
責(zé)任分工
由于每個(gè)節(jié)點(diǎn)只關(guān)注一個(gè)主要問(wèn)題,因此簡(jiǎn)化了整個(gè)系統(tǒng)的復(fù)雜性。 所有組件之間的交互最少,集群內(nèi)通信故障(在讀取過(guò)程中)幾乎不影響可用性。 群集通過(guò)Zookeeper保持同步。 即使Zookeeper掉線了,盡管您將無(wú)法創(chuàng)建任何新的段,從而影響寫入,但讀取仍然可能發(fā)生。
列式存儲(chǔ)
由于druid是為分析查詢而設(shè)計(jì)的,因此它以列導(dǎo)向的格式存儲(chǔ)數(shù)據(jù)。 面向列的格式可以提供更好的壓縮率(因?yàn)閱蝹€(gè)列中的大多數(shù)數(shù)據(jù)都是相似的)和更好的查詢性能,因?yàn)橥ǔT诜治霾樵冎胁⒎撬辛卸伎梢栽L問(wèn),因此僅加載實(shí)際需要的數(shù)據(jù)。 對(duì)于字符串列,德魯伊通常執(zhí)行字典編碼,然后應(yīng)用LZF壓縮以減小數(shù)據(jù)大小。
防止不必要的掃描
Druid維護(hù)著一個(gè)字符串值的倒排索引,這樣我們就可以知道在哪個(gè)行中可以看到一個(gè)特定的值。 這允許僅掃描其中存在值的那些行。

上表的倒排索引看起來(lái)像
Foo:[1,0,0,1,0,1]
Bar:[0,1,1,0,1,0]
其中1表示索引中的行中存在特定鍵。 如果要掃描包含F(xiàn)oo和Bar的所有行,只需對(duì)兩個(gè)索引進(jìn)行OR。
基數(shù)估計(jì)
為了獲得準(zhǔn)確的基數(shù)匯總,例如確定每分鐘訪問(wèn)您的站點(diǎn)的唯一用戶數(shù),您需要將用戶存儲(chǔ)在某種數(shù)據(jù)結(jié)構(gòu)(例如HashSet)中,然后對(duì)其中的元素總數(shù)進(jìn)行計(jì)數(shù)。 但是,這導(dǎo)致大量空間需求。
另一方面,Druid使用HyperLogLog對(duì)其進(jìn)行性能測(cè)試,其準(zhǔn)確性約為97%。 對(duì)于大多數(shù)運(yùn)行分析查詢的人來(lái)說(shuō),這通常很好。 您甚至可以通過(guò)在索引過(guò)程中在攝取時(shí)預(yù)先計(jì)算HLL來(lái)使其更快。
預(yù)聚集
德魯伊可以在攝取時(shí)預(yù)聚合數(shù)據(jù)(稱為匯總)。 這減少了存儲(chǔ)數(shù)據(jù)的大小,也使聚合查詢快得多。 在這種情況下,您確實(shí)會(huì)丟失每行信息,這就是為什么可以在攝取期間將其禁用。
快取
Druid在代理上維護(hù)每個(gè)段的查詢緩存,這有助于快速返回結(jié)果。 它還在歷史和實(shí)時(shí)服務(wù)器中緩存數(shù)據(jù),以加快掃描速度。

> Per segment Cache (from druid whitepaper)
負(fù)載均衡
協(xié)調(diào)器以這樣的方式分配段:在歷史節(jié)點(diǎn)之間不偏斜段。 它考慮了數(shù)據(jù)大小,源和新近度,因此還提供了最佳性能,例如 普通查詢涵蓋了跨越最近創(chuàng)建的細(xì)分的單個(gè)數(shù)據(jù)源,因此明智的做法是以更高的速率復(fù)制最近創(chuàng)建的細(xì)分,以使這些查詢可以由多個(gè)節(jié)點(diǎn)提供服務(wù)。
基于時(shí)間的分區(qū)
Druid需要用于數(shù)據(jù)分發(fā)和保留的必填時(shí)間戳列。 包含一年中分布的時(shí)間戳的數(shù)據(jù)按天劃分更好,而具有一天中分布的時(shí)間戳的數(shù)據(jù)按小時(shí)劃分更好。 此時(shí)間戳還用于在寫入Druid時(shí)忽略舊事件。 按時(shí)間分區(qū)還有助于更好地分配和復(fù)制段。
如果您想了解有關(guān)Druid的更多信息,請(qǐng)參考以下鏈接:
- · 官方文件
- · 德魯伊:實(shí)時(shí)分析數(shù)據(jù)存儲(chǔ)(白皮書)
- · Druid簡(jiǎn)介,您的交互式Analytics(大規(guī)模)
- · MetaMarkets-楊德進(jìn)在YouTube上對(duì)Druid的介紹
(本文翻譯自Kartik Khare的文章《What Makes Apache Druid Great for Realtime Analytics?》,參考:
https://codeburst.io/what-makes-apache-druid-great-for-realtime-analytics-61f817ee5ff6)