導讀
背景
eBay廣告資料平臺為eBay第一方廣告主(使用Promoted Listing服務的賣家)提供了廣告流量、使用者行為和效果資料分析功能。廣告賣家透過賣家中心(Seller Hub)的營銷標籤頁、效果標籤頁和公開API,有效掌控和對比店鋪的營銷活動和推廣商品的流量、銷量的實時和歷史資料,並透過網頁或者API 下載資料分析報告。
這一系統上線之初使用了自研的分散式SQL引擎,構建在物件儲存系統之上。3年前隨著廣告流量增加,我們把資料引擎切換到Druid上。
這一平臺的主要挑戰如下:
資料量大 : 每日的插入資料記錄有數百億條,每秒的插入峰值接近一百萬條;離線資料攝入 :在不影響實時資料攝入的情況下,每天需要對前1-2天的資料進行線上替換。根據上游資料團隊釋出清洗過的每日資料,廣告資料平臺需要在不影響查詢的情況下每日替換實時資料,資料切換要求實現跨節點的全域性原子操作;完整性和一致性 :面向賣家的財務資料,離線更新後的資料要求不能有遺漏和重複;實時資料要求端對端的延遲在十秒內。Druid VS. ClickHouse
Druid於2011年由Metamarkets開發,是一款高效能列式線上分析和儲存引擎。它於2012年開源,2015年成為Apache基金會旗下專案。Druid在業界使用廣泛,為千億級資料提供亞秒級的查詢延遲,擅長高可用、水平擴充套件;另外為資料攝入提供了很多非常方便的聚合、轉換模版,內建支援多種資料來源,最快可以在幾十分鐘內配置好新的資料表,包括資料定義和資料攝入鏈路(Lambda架構),大大提高了開發效率。
ClickHouse由俄羅斯最大的搜尋引擎公司Yandex研發,設計目標是支援Yandex.Metrica(世界第二大Web分析平臺)生成使用者分析報表等核心功能。ClickHouse是一個數據庫管理系統(DBMS),有資料庫、表、檢視、DDL、DML等概念,並提供了較為完整的SQL支援。其核心特性有如下幾點:
高效的資料儲存 : 透過資料壓縮和列式儲存,可以達到最高10倍的資料壓縮率;高效的資料查詢 : 透過主鍵索引、向量化引擎處理、多處理器併發和分散式查詢,最大壓榨CPU的所有能力,在中小規模的資料量上尤為突出;靈活的資料定義和 接 入 : 透過支援SQL語言、JDBC和關係模型,降低學習和遷移成本,可以和其他現有資料的產品無縫整合。為什麼遷移?
運維
Druid雖然提供了很多非常方便的資料攝入功能,但它的元件構成也較為複雜,節點型別有6種(Overload, Coordinator, Middle Manager, Indexer, Broker和Historical)。除了自身的節點,Druid還依賴於MySQL儲存元資料資訊、Zookeeper選舉Coordinator和Overlord、HDFS備份歷史資料。
ClickHouse的架構採用了對等節點的設計,節點只有一種型別,沒有主從節點。如果使用了副本功能,則依賴於Zookeeper儲存資料段的同步進度。
與此同時,eBay的基礎架構團隊提出在定製ClickHouse的基礎上,向產品團隊提供列式資料庫儲存的服務。除了運維和生命週期管理,基礎架構團隊對ClickHouse進行改造和二次開發,進一步提高了資料攝入和儲存的效率,並在離線攝入方面彌補了和Druid的功能差距。
延時資料插入
Druid透過引入實時資料的索引任務,把實時資料處理成一個個分段資料(segment),並歸檔成歷史資料。成為分段資料之後,該時段資料即不可寫入。由於併發實時索引任務數的限制,我們設定了3個小時的視窗長度(每個小時一個任務),因此超過3個小時的資料就無法寫入。在某些極端情況下,例如上游資料延遲或者實時資料消費過於滯後,就會導致離線資料替換前這部分資料的缺失。ClickHouse則沒有這個限制,任意分割槽都可以隨時寫入。
主鍵最佳化
ClickHouse支援的主鍵並不是傳統意義下關係型資料庫的主鍵。傳統的主鍵要求每條表記錄都有唯一的鍵值,透過查詢主鍵可以唯一地查詢到一條表記錄。而在ClickHouse中,主鍵定義了記錄在儲存中排序的順序,允許重複,所以稱之為排序鍵似乎更加合理。事實上在ClickHouse裡的主鍵定義透過ORDER BY宣告,僅在個別場景中允許和排序鍵不一致(但必須是排序鍵的字首)。
由於我們的產品是給賣家提供分析功能,幾乎所有的查詢限定在了單一賣家維度,因此透過主鍵按照賣家排序,可以極大地提高查詢效率以及資料壓縮率。
系統架構
如上圖 所示,系統由4個部分組成:
實時資料獲取模組,接入eBay的行為和交易實時訊息平臺;離線資料替換模組,接入eBay內部的資料倉庫平臺;ClickHouse部署和外圍資料服務;報表服務,支撐廣告主、商家後臺和eBay公開API。實戰經歷
Schema 設計
ClickHouse提供了豐富的schema配置。這方面需要根據業務場景和資料模式反覆斟酌和多次試驗,因為不同的選擇會對儲存和效能有數量級的影響,一個錯誤的選擇會導致後期巨大的調優和變更成本。
1)表引擎ClickHouse的儲存引擎的核心是合併樹(MergeTree),以此為基礎衍生出彙總合併樹(SummingMergeTree),聚合合併樹(AggregationMergeTree),版本摺疊樹(VersionCollapsingTree)等常用的表引擎。另外上述所有的合併樹引擎都有複製功能(ReplicatedXXXMergeTree)的對應版本。
我們的廣告資料平臺的展示和點選資料選擇了複製彙總合併樹。這兩類使用者行為資料量極大,減小資料量節省儲存開銷並提升查詢效率是模式設計的主要目標。ClickHouse在後臺按照給定的維度彙總資料,降低了60%的資料量。銷售資料選擇了普通的複製合併樹,一方面由於銷售資料對某些指標有除彙總以外的聚合需求,另一方面由於本身資料量不大,合併資料的需求並不迫切。
2)主鍵一般情況下,ClickHouse表的主鍵(Primary Key)和排序鍵(Order By Key)相同,但是採用了彙總合併樹引擎(SummingMergeTree)的表可以單獨指定主鍵。把一些不需要排序或者索引功能的維度欄位從主鍵裡排除出去,可以減小主鍵的大小(主鍵執行時需要全部載入到記憶體中),提高查詢效率。
3)壓縮ClickHouse支援列級別的資料壓縮,顯著地減少原始資料的儲存量,這也是列儲存引擎的巨大優勢。查詢階段,較小的儲存佔用也可以減少IO量。對不同列選擇一種合適的壓縮演算法和等級,能把壓縮和查詢的平衡做到價效比最優。
ClickHouse的所有列預設使用LZ4壓縮。除此以外,一般的資料列可以選擇更高壓縮率的演算法如LZ4HC,ZSTD;而對於類似時間序列的單調增長資料可以選擇DoubleDelta, Gorilla等特殊壓縮演算法。LZ4HC和ZSTD等高壓縮率的演算法還可以自己選擇壓縮級別。在我們的生產資料集上,ZSTD演算法對String型別欄位壓縮效果較為顯著。LZ4HC是LZ4的高壓縮比改進版,更適用於非字串型別。
更高的壓縮率意味著更少的儲存空間,同時由於降低了查詢的IO量,可以間接提升查詢效能。不過CPU也不是大風颳來的,資料的插入效能就成了犧牲品。根據我們內部測試的資料,在我們的生產資料集上使用LZ4HC(6)相比LZ4可以節省30%的資料,但實時資料攝取效能下降了60%。
4)低基值得一提的是,對於基數較低的列(即列值多樣性低),可以使用LowCardinality來降低原始儲存空間(從而降低最終儲存空間)。如果在使用壓縮演算法的情況下對一字串型別的列使用LowCardinality,還能再縮小25%的空間量。
在我們的測試資料集上,如果整表組合使用LowCardinality、LZ4HC(6)和ZSTD(15),整體壓縮比大約在原來的13%左右。
離線資料替換
1)挑戰針對廣告主的資料報表要求資料準確、一致。實時的行為資料存在少量的bot資料(需要離線清除),另外廣告的歸因也需要在離線階段重新調整,因此我們引入了離線資料鏈路,在實時資料寫入24-72小時之後,用離線資料替換實時資料。其中的挑戰如下:
廣告系統每天需要處理的使用者離線資料量近1TB,在此之前,需要耗費大量時間將資料從Hadoop匯入Druid。另外,匯入期間的I/O、CPU和記憶體的開銷對查詢的壓力不小。如何在保證資料一致性的同時,亦確保資料遷移的效率,是問題的關鍵;如何在資料替換期間,確保使用者可見的資料波動最小。這就要求資料替換操作是原子性的,或者至少對每個廣告主都是原子的;除了日常的離線資料更新,在資料倉庫資料出現偏差遺漏時,需要支援大範圍的資料修正和補償。作業排程要求保證日常工作及時完成,並儘快完成資料修正工作。此外還需要監控資料更新中的各種指標,以應對各種突發狀況。Druid原生支援資料離線更新服務,我們與基礎架構團隊合作,在ClickHouse平臺實現了這一功能。
2)資料架構對於整合線上資料和離線資料的大資料架構,業界通常的做法是Lambda架構。即離線層和線上層分別匯入資料,在展示層進行資料的合併。
我們也大致上採用了這一架構。但具體的做法和經典有所不同。ClickHouse裡資料分割槽(partition)是一個獨立的資料儲存單元,每一個分割槽都可以單獨從現有表裡脫離(detach)、引入(attach)和替換(replace)。分割槽的條件可以自定義,一般按照時間劃分。透過對資料表內資料分割槽的單個替換,我們可以做到查詢層對底層資料更新的透明,也不需要額外的邏輯進行資料合併。
3)Spark聚合與分片為了降低ClickHouse匯入離線資料效能壓力,我們引入了Spark任務對原始離線資料進行聚合和分片。每個分片可以分別拉取並匯入資料檔案,節省了資料路由、聚合的開銷。
4)資料更新任務管理A. 鎖定分割槽拓撲結構在處理資料前,離線資料更新系統向基礎架構團隊提供的服務請求鎖定ClickHouse的分割槽拓撲結構,在此期間該分割槽的拓撲結構不會改變。服務端根據預先定義好的資料表結構與分割槽資訊返回資料的分片邏輯與分片ID。離線資料更新系統根據拓撲資訊提交Spark任務。多張表的資料處理透過Spark並行完成,顯著提升了資料更新的速度。
B. 資料聚合與分片對於每一張需要更新的表,啟動一個Spark任務對資料進行聚合與分片。根據ClickHouse服務端返回的表結構與分片拓撲將資料寫入Hadoop,同時輸出資料替換階段用於校驗一致性的checksum與分片行數。系統透過Livy Server API提交併輪詢任務狀態,在有任務失敗的情況下進行重試,以排除Spark叢集資源不足導致的任務失敗。離線資料更新不但要滿足每天的批次資料更新需求,還需要支援過往資料的再次更新,以便同步上游資料在日常定時任務更新之外的資料變動。
我們利用平臺團隊封裝的Spring Batch管理更新任務,按照日期將每天的資料劃分為一個子任務。透過Spring Batch實現的Continuously Job保證在同一時刻子任務在執行的唯一性,避免產生任務競爭問題。對於過往資料的更新,我們將Batch任務分類,除了日常任務之外,還可以手動觸發給定時間範圍內的資料修正任務(如下圖)。
C. 資料替換在子任務中的所有Spark Job完成後,離線資料更新系統會呼叫基礎架構團隊提供的資料替換介面,發起資料替換請求。服務端按照定義好的分割槽,將資料從Hadoop直接寫入ClickHouse,如圖3所示。
離線資料更新系統的架構如圖4所示。MySQL資料庫用於記錄資料替換過程中任務的狀態與優先順序,當Spark Job失敗或者由於其他原因導致替換任務失敗重啟後,恢復任務的進度。
5) 原子性與一致性為了保證資料替換的原子性,基礎架構團隊提供了分割槽替換的方式。在離線資料匯入的過程中,首先建立目標分割槽的臨時分割槽。當資料替換完畢並且校驗完成之後,目標分割槽會被臨時分割槽替換。針對不同機器上不同分片的原子性替換問題,基礎架構團隊為每一條資料引入了資料版本。對於每一個數據分割槽,都有對應的活躍版本號。直到待替換資料分割槽的所有分片都成功匯入之後,分割槽的版本號進行更新。上游應用的同一條SQL只能讀取同一分割槽一個版本的資料,每個分割槽的資料替換隻感覺到一次切換,並不會出現同時讀取新舊資料的問題。
廣告平臺報表生成應用因此在SQL層面引入了相應的修改,透過引入固定的WITH和PREWHERE語句,在字典中查詢出每個資料分割槽對應的版本號,並在查詢計劃中排除掉不需要的資料分割槽。
為了確保資料替換的一致性,在完成Spark資料處理之後,離線資料更新系統會計算各資料分片的校驗碼與資料總量。當替換完畢之後,ClickHouse服務端會對分片資料進行校驗,確保在資料搬遷過程中沒有資料丟失和重複。
資料查詢
ClickHouse支援SQL查詢(不完全),有HTTP和TCP兩種連線方式,官方和第三方的查詢工具和庫豐富。使用者可以使用命令列,JDBC或者視覺化工具快速進行資料查詢的開發和除錯。ClickHouse透過MPP(Massively Parallel Processing) + SMP(Symmetric Multiprocessing)充分地利用機器資源,單條查詢語句預設使用機器核數一半的CPU,因此ClickHouse不支援高併發的應用場景。在業務使用層面,最核心的問題是查詢校驗和併發控制,單條過大的查詢或者過高的併發都會導致叢集資源使用率過高,影響叢集穩定性。
應用架構
Ebay Seller Hub透過Reports Service接入ClickHouse查詢,Reports Service提供了Public和Internal兩套API。Internal API提供給Seller Hub以及其他內部的已知應用使用,Public API在eBay Developers Program(詳情見:https://developer.ebay.com/)開放給第三方開發者。
Internal API的查詢直接提交內部執行緒池執行,執行緒池的大小根據ClickHouse的叢集機器數量設定。查詢請求執行前會進行校驗,過濾所有非法以及資源不可預估的請求。
Public API透過任務提交的方式非同步執行查詢,使用者提交的查詢任務存入DB中,Service內部的Schedule定時掃表,根據任務的狀態序列執行查詢任務。執行成功的任務上傳生成Report到檔案伺服器,使用者拿到URL後自行下載。執行失敗的任務,根據錯誤型別(非法的請求,資源不足等)來選擇是否在下一個週期再次執行。
測試釋出
在生產環境部署完成後,我們開啟了資料雙寫,往ClickHouse裡不斷地插入實時資料和離線資料,直到達到Druid的資料水平。在資料一致性驗證過後,我們映象了一份生產服務的查詢,然後把這些查詢轉發給ClickHouse。透過收集和對比Druid和ClickHouse的響應,我們能夠驗證ClickHouse鏈路的資料質量和查詢效能。之後的灰度階段,我們逐漸提升ClickHouse服務生產系統的比例,並保持Druid繼續執行,以保證出現問題可以及時回滾。
查詢GUI
資料視覺化方面,我們需要提供類似Turnilo的視覺化工具給開發、測試和BI人員使用。ClickHouse支援多種商業和開源的產品接入,我們選用了Cube.JS,並進行了簡單的二次開發。
總結
本文介紹了廣告資料平臺的基本情況,ClickHouse/Druid的特點對比和團隊使用ClickHouse替換Druid的架構方案。ClickHouse表現出了良好的效能和擴充套件能力,並且還在快速的迭代更新。目前專案已經上線,接下來我們還會和大家繼續分享過程中的碰到的一些問題和解決方法,歡迎大家持續關注。
作者丨吳寒思 周路 餘何