一、Druid介紹
Druid 是 MetaMarket 公司研發,專為海量資料集上的做高效能 OLAP (OnLine Analysis Processing)而設計的資料儲存和分析系統,目前Druid 已經在Apache基金會下孵化。Druid的主要特性:
- 互動式查詢( Interactive Query ): Druid 的低延遲資料攝取架構允許事件在它們建立後毫秒內查詢,因為 Druid 的查詢延時通過只讀取和掃描有必要的元素被優化。Druid 是列式儲存,查詢時讀取必要的資料,查詢的響應是亞秒級響應。
- 高可用性( High Available ):Druid 使用 HDFS/S3 作為 Deep Storage,Segment 會在2個 Historical 節點上進行載入;攝取資料時也可以多副本攝取,保證資料可用性和容錯性。
- 可伸縮( Horizontal Scalable ):Druid 部署架構都可以水平擴充套件,增加大量伺服器來加快資料攝取,以及保證亞秒級的查詢服務
- 並行處理( Parallel Processing ): Druid 可以在整個叢集中並行處理查詢
- 豐富的查詢能力( Rich Query ):Druid支援 Scan、 TopN、 GroupBy、 Approximate 等查詢,同時提供了2種查詢方式:API 和 SQL
Druid常見應用的領域:
- 網頁點選流分析
- 網路流量分析
- 監控系統、APM
- 資料運營和營銷
- BI分析/OLAP
二、為什麼我們需要用 Druid
有贊作為一家 SaaS 公司,有很多的業務的場景和非常大量的實時資料和離線資料。在沒有是使用 Druid 之前,一些 OLAP 場景的場景分析,開發的同學都是使用 SparkStreaming 或者 Storm 做的。用這類方案會除了需要寫實時任務之外,還需要為了查詢精心設計儲存。帶來問題是:開發的週期長;初期的儲存設計很難滿足需求的迭代發展;不可擴充套件。
在使用 Druid 之後,開發人員只需要填寫一個資料攝取的配置,指定維度和指標,就可以完成資料的攝入;從上面描述的 Druid 特性中我們知道,Druid 支援 SQL,應用 APP 可以像使用普通 JDBC 一樣來查詢資料。通過有贊自研OLAP平臺的幫助,資料的攝取配置變得更加簡單方便,一個實時任務建立僅僅需要10來分鐘,大大的提高了開發效率。
2.1、Druid 在有贊使用場景
- 系統監控和APM:有讚的監控系統(天網)和大量的APM系統都使用了 Druid 做資料分析
- 資料產品和BI分析:有贊 SaaS 服務為商家提供了有很多資料產品,例如:商家營銷工具,各類 BI 報表
- 實時OLAP服務:Druid 為風控、資料產品等C端業務提供了實時 OLAP 服務
三、Druid的架構
Druid 的架構是 Lambda 架構,分成實時層( Overlord、 MiddleManager )和批處理層( Broker 和 Historical )。主要的節點包括(PS: Druid 的所有功能都在同一個軟體包中,通過不同的命令啟動):
- Coordinator 節點:負責叢集 Segment 的管理和釋出,並確保 Segment 在 Historical 叢集中的負載均衡
- Overlord 節點:Overlord 負責接受任務、協調任務的分配、建立任務鎖以及收集、返回任務執行狀態給客戶端;在Coordinator 節點配置 asOverlord,讓 Coordinator 具備 Overlord 功能,這樣減少了一個元件的部署和運維
- MiddleManager 節點:負責接收 Overlord 分配的索引任務,建立新啟動Peon例項來執行索引任務,一個MiddleManager可以執行多個 Peon 例項
- Broker 節點:負責從客戶端接收查詢請求,並將查詢請求轉發給 Historical 節點和 MiddleManager 節點。Broker 節點需要感知 Segment 資訊在叢集上的分佈
- Historical 節點:負責按照規則載入非實時視窗的Segment
- Router 節點:可選節點,在 Broker 叢集之上的API閘道器,有了 Router 節點 Broker 不在是單點服務了,提高了併發查詢的能力
四、有贊 OLAP 平臺的架構和功能解析
4.1 有贊 OLAP 平臺的主要目標:
- 最大程度的降低實時任務開發成本:從開發實時任務需要寫實時任務、設計儲存,到只需填寫配置即可完成實時任務的建立
- 提供資料補償服務,保證資料的安全:解決因為實時視窗關閉,遲到資料的丟失問題
- 提供穩定可靠的監控服務:OLAP 平臺為每一個 DataSource 提供了從資料攝入、Segment 落盤,到資料查詢的全方位的監控服務
4.2 有贊 OLAP 平臺架構
有贊 OLAP 平臺是用來管理 Druid 和周圍元件管理系統,OLAP平臺主要的功能:
- Datasource 管理
- Tranquility 配置和例項管理:OLAP 平臺可以通過配置管理各個機器上 Tranquility 例項,擴容和縮容
- 資料補償管理:為了解決資料遲延的問題,OLAP 平臺可以手動觸發和自動觸發補償任務
- Druid SQL查詢: 為了幫助開發的同學除錯 SQL,OLAP 平臺整合了 SQL 查詢功能
- 監控報警
4.2 Tranquility 例項管理
OLAP 平臺採用的資料攝取方式是 Tranquility工具,根據流量大小對每個 DataSource 分配不同 Tranquility 例項數量; DataSource 的配置會被推送到 Agent-Master 上,Agent-Master 會收集每臺伺服器的資源使用情況,選擇資源豐富的機器啟動 Tranquility 例項,目前只要考慮伺服器的記憶體資源。同時 OLAP 平臺還支援 Tranquility 例項的啟停,擴容和縮容等功能。
4.3 解決資料遲延問題——離線資料補償功能
流式資料處理框架都會有時間視窗,遲於視窗期到達的資料會被丟棄。如何保證遲到的資料能被構建到 Segment 中,又避免實時任務視窗長期不能關閉。我們研發了 Druid 資料補償功能,通過 OLAP 平臺配置流式 ETL 將原始的資料儲存在 HDFS 上,基於 Flume 的流式 ETL 可以保證按照 Event 的時間,同一小時的資料都在同一個檔案路徑下。再通過 OLAP 平臺手動或者自動觸發 Hadoop-Batch 任務,從離線構建 Segment。
基於 Flume 的 ETL 採用了 HDFS Sink 同步資料,實現了 Timestamp 的 Interceptor,按照 Event 的時間戳欄位來建立檔案(每小時建立一個資料夾),延遲的資料能正確歸檔到相應小時的檔案中。
4.4 冷熱資料分離
隨著接入的業務增加和長期的執行時間,資料規模也越來越大。Historical 節點載入了大量 Segment 資料,觀察發現大部分查詢都集中在最近幾天,換句話說最近幾天的熱資料很容易被查詢到,因此資料冷熱分離對提高查詢效率很重要。Druid 提供了Historical 的 Tier 分組機制與資料載入 Rule 機制,通過配置能很好的將資料進行冷熱分離。 首先將 Historical 群進行分組,預設的分組是"_default_tier",規劃少量的 Historical 節點,使用 SATA 盤;把大量的 Historical 節點規劃到 "hot" 分組,使用 SSD 盤。然後為每個 DataSource 配置載入 Rule :
- rule1: 載入1份最近30天的 Segment 到 "hot" 分組;
- rule2: 載入2份最近6個月的 Segment 到 "_default_tier" 分組;
- rule3: Drop 掉之前的所有 Segment(注:Drop 隻影響 Historical 載入 Segment,Drop 掉的 Segment 在 HDFS 上仍有備份)
{"type":"loadByPeriod","tieredReplicants":{"hot":1}, "period":"P30D"}
{"type":"loadByPeriod","tieredReplicants":{"_default_tier":2}, "period":"P6M"}
{"type":"dropForever"}
複製程式碼
提高 "hot"分組叢集的 druid.server.priority 值(預設是0),熱資料的查詢都會落到 "hot" 分組。
4.5 監控與報警
Druid 架構中的各個元件都有很好的容錯性,單點故障時叢集依然能對外提供服務:Coordinator 和 Overlord 有 HA 保障;Segment 是多副本儲存在HDFS/S3上;同時 Historical 載入的 Segment 和 Peon 節點攝取的實時部分資料可以設定多副本提供服務。同時為了能在節點/叢集進入不良狀態或者達到容量極限時,儘快的發出報警資訊。和其他的大資料框架一樣,我們也對 Druid 做了詳細的監控和報警項,分成了2個級別:
- 基礎監控
包括各個元件的服務監控、叢集水位和狀態監控、機器資訊監控 - 業務監控
業務監控包括:實時任務建立、資料攝取 TPS、消費遲延、持久化相關、查詢 RT/QPS 等的關鍵指標,有單個 DataSource 和全域性的2種不同檢視;同時這些監控項都有設定報警項,超過閾值觸發報警提醒。業務指標的採集是大部分是通過 Druid 框架自身提供的 Metrics 和 Alerts 資訊,然後流入到 Kafka / OpenTSDB 等元件,通過流資料分析獲得我們想要的指標。
4.6 部署架構
Historical 叢集的部署和4.4節中描述的資料冷熱分離相對應,用 SSD 叢集儲存最近的N天的熱資料(可調節 Load 的天數),用相對廉價的 Sata 機型儲存更長時間的歷史冷資料,同時充分利用 Sata 的 IO 能力,把 Segment Load到不同磁碟上;在有贊有很多的收費業務,我們在硬體層面做隔離,保證這些業務在查詢端有足夠的資源;在接入層,使用 Router 做路由,避免了 Broker 單點問題,也能很大的程度叢集查詢吞吐量;在 MiddleManager 叢集,除了部署有 Index 任務(記憶體型任務)外,我們還混合部署了部分流量高 Tranquility 任務(CPU型任務),提高了 MiddleManager 叢集的資源利用率。
4.7 貢獻開源社群
在有贊業務查詢方式一般是 SQL On Broker/Router,我們發現一旦有少量慢查詢的情況,客戶端會出現查詢不響應的情況,而且連線越來越難獲取到。登入到Broker 的服務端後發現,可用連線數量急劇減少至被耗盡,同時出現了大量的 TCP Close_Wait。用 jstack 工具排查之後發現有 deadlock 的情況,具體的 Stack 請檢視 ISSUE-6867。
經過原始碼排查之後發現,DruidConnection為每個 Statement 註冊了回撥。在正常的情況下 Statement 結束之後,執行回撥函式從 DruidConnection 的 statements 中 remove 掉自己的狀態;如果有慢查詢的情況(超過最長連線時間或者來自客戶端的Kill),connection 會被強制關閉,同時關閉其下的所有 statements ,2個執行緒(關閉connection的執行緒和正在退出 statement 的執行緒)各自擁有一把鎖,等待對方釋放鎖,就會產生死鎖現象,連線就會被馬上耗盡。
// statement執行緒退出時執行的回撥函式
final DruidStatement statement = new DruidStatement(
connectionId,
statementId,
ImmutableSortedMap.copyOf(sanitizedContext),
() -> {
// onClose function for the statement
synchronized (statements) {
log.debug("Connection[%s] closed statement[%s].", connectionId, statementId);
statements.remove(statementId);
}
}
);
複製程式碼
// 超過最長連線時間的自動kill
return connection.sync(
exec.schedule(
() -> {
log.debug("Connection[%s] timed out.", connectionId);
closeConnection(new ConnectionHandle(connectionId));
},
new Interval(DateTimes.nowUtc(), config.getConnectionIdleTimeout()).toDurationMillis(),
TimeUnit.MILLISECONDS
)
);
複製程式碼
在排查清楚問題之後,我們也向社群提了 PR-6868 。目前已經成功合併到 Master 分支中,將會 0.14.0 版本中釋出。如果讀者們也遇到這個問題,可以直接把該PR cherry-pick 到自己的分支中進行修復。
五、挑戰和未來的展望
5.1 資料攝取系統
目前比較常用的資料攝取方案是:KafkaIndex 和 Tranquility 。我們採用的是 Tranquility 的方案,目前 Tranquility 支援了 Kafka 和 Http 方式攝取資料,攝取方式並不豐富;Tranquility 也是 MetaMarket 公司開源的專案,更新速度比較緩慢,不少功能缺失,最關鍵的是監控功能缺失,我們不能監控到例項的執行狀態,攝取速率、積壓、丟失等資訊。 目前我們對 Tranquility 的例項管理支援啟停,擴容縮容等操作,實現的方式和 Druid 的 MiddleManager 管理 Peon 節點是一樣的。把 Tranquility 或者自研攝取工具轉換成 Yarn 應用或者 Docker 應用,就能把資源排程和例項管理交給更可靠的排程器來做。
5.2 Druid 的維表 JOIN 查詢
Druid 目前並不沒有支援 JOIN查詢,所有的聚合查詢都被限制在單 DataSource 內進行。但是實際的使用場景中,我們經常需要幾個 DataSource 做 JOIN 查詢才能得到所需的結果。這是我們面臨的難題,也是 Druid 開發團隊遇到的難題。
5.3 整點查詢RT毛刺問題
對於 C 端的 OLAP 查詢場景,RT 要求比較高。由於 Druid 會在整點建立當前小時的 Index 任務,如果查詢正好落到新建的 Index 任務上,查詢的毛刺很大,如下圖所示:
我們已經進行了一些優化和調整,首先調整 warmingPeriod 引數,整點前啟動 Druid 的 Index 任務;對於一些 TPS 低,但是 QPS 很高的 DataSource ,調大 SegmentGranularity,大部分 Query 都是查詢最近24小時的資料,保證查詢的資料都在記憶體中,減少新建 Index 任務的,查詢毛刺有了很大的改善。儘管如此,離我們想要的目標還是一定的差距,接下去我們去優化一下原始碼。
5.4 歷史資料自動Rull-Up
現在大部分 DataSource 的 Segment 粒度( SegmentGranularity )都是小時級的,儲存在 HDFS 上就是每小時一個Segment。當需要查詢時間跨度比較大的時候,會導致Query很慢,佔用大量的 Historical 資源,甚至出現 Broker OOM 的情況。如果建立一個 Hadoop-Batch 任務,把一週前(舉例)的資料按照天粒度 Rull-Up 並且 重新構建 Index,應該會在壓縮儲存和提升查詢效能方面有很好的效果。關於歷史資料 Rull-Up 我們已經處於實踐階段了,之後會專門博文來介紹。
最後打個小廣告,有贊大資料團隊基礎設施團隊,主要負責有讚的資料平臺 (DP), 實時計算 (Storm, Spark Streaming, Flink),離線計算 (HDFS, YARN, HIVE, SPARK SQL),線上儲存(HBase),實時 OLAP (Druid) 等數個技術產品,歡迎感興趣的小夥伴聯絡 zhaojiandong@youzan.com