Apache Druid 在 Shopee 的雲原生架構演進

danny_2018發表於2022-10-26

Shopee Druid 為什麼要演進到雲原生架構?如果要實現雲原生化,需要做哪些事情?在這個過程中,可能還會踩到哪些坑?

本次分享將圍繞以上三點展開,從 Shopee Druid 物理機架構遇到的問題入手,探索雲原生架構的優勢,並重點介紹雲原生架構設計的技術細節,以及一些落地的最佳實踐。

在 ApacheCon Asia 2022 中,來自 Shopee Data Infra 團隊的 Jiayi 分享了 Apache Druid 在 Shopee 的雲原生架構演進。本文根據演講內容整理而成。

1. 背景

1.1 Druid 介紹

Druid 是一款高效能的、實時的、分析型資料庫。它的高效能主要體現在這幾方面:列式儲存、Bitmap 倒排索引、資料壓縮、SIMD 向量化加速、快取系統等等。

而 Druid 的 Lambda 架構,也使得其能夠支援實時資料的寫入和查詢。

同時,Druid 作為一個 OLAP 引擎,內建了豐富的查詢運算元,以滿足各式各樣的分析需求。

透過這副 Venn 圖可以看到,Druid 兼備了時序資料庫、資料倉儲和全文檢索引擎的部分特點:

如果你對 TSDB 比較瞭解,那麼可以將 Druid 近似理解為 InfluxDB;

如果你對數倉更加專業,那麼也可以簡單類比為 Hive;

而如果你對全文檢索更為熟悉,則可以想象成 ElasticSearch。

當然,實際的內在設計細節,是有諸多不同的,這裡就不展開講了。

1.2 基於物理機架構

對 Druid 有了初步的認識之後,先來看一下,之前 Shopee 基於物理機架構構建的服務,遇到了哪些問題。

主要包括以下幾方面:穩定性問題、效率問題、成本問題和安全問題。

1.2.1 穩定性問題

首先是穩定性問題。

這是一幅 Druid 查詢 QPS 的監控圖。可以看到,在臨近中午 12 點的時候,突然出現一個查詢流量的尖峰,遠遠超過了預先設定的告警閾值。之後我們定位的結論是,業務指令碼程式的 Bug 導致傳送了很多無意義的請求。

相信大家也或多或少地遇到過類似的情況。比如臨近飯點了,甚至還在就餐中,突然就接到一波告警電話,也只能廢寢忘食地解決線上問題;而如果故障的時間很不巧是在半夜,那麼告警電話就會變成“奪命連環 call”。

接下來看另外一個例子。

這幅截圖是我們 Druid 慢查詢指令碼的某一次告警資訊。其中大部分內容都進行了脫敏,我們只需要關注高亮的部分。從中可以看出,這是一條嘗試分析過去一整年資料的重查詢,其消耗了大量的伺服器資源,導致其他業務的正常查詢請求都受到了影響。

尤其是當我們將 SQL 客戶端開放給終端使用者之後,甚至還會遇到那麼一兩個不帶任何限制條件的 select * 查詢。這無異於發起了一波猛烈的 DDoS 攻擊。

類似的穩定性案例還有很多,主要可以分為三類。

第一類是查詢相關的,例如:

一個查詢中巢狀了太多層的子查詢;

一次性查詢了過去好幾年的冷資料;

對兩個或者更多的事實表進行 Join 查詢;

亦或是由於程式故障,在瞬間發起了大量的查詢請求。

第二類是寫入相關的,例如:

寫入任務中配置的分割槽數過多;

寫入的 TPS 速率過快;

寫入任務的資料體量過大。

第三類是叢集自身的,例如:

我們透過 Crontab 定時檢測服務狀態,並自動重啟進行故障恢復,但是週期往往是分鐘級的。

可見,穩定性問題的誘因是非常多的,實在是防不勝防。

1.2.2 效率問題

分析完了穩定性問題,我們再來看下效率問題。

當因為資源不足而導致效能瓶頸時,我們需要和業務的對應負責人臨時溝通,交流的時間成本是比較高的,因此效率也是比較低的。即便我們完成了資訊同步和溝通,擴縮容的操作需要人為介入,也很難在短時間內完成。

而所有業務同在一個大叢集中,導致很難給業務進行優先順序排序。因為對於每一個業務而言,自身肯定是最重要的。從而,我們也就很難實現自動的流量降級。

而效率問題,最終會使得業務的發展受到嚴重製約。

1.2.3 成本問題

接下來是企業需要納入考量的成本問題。

首先來看機器資源的成本:

我們往往會因為埠衝突、資源偏好相同等原因,使得在同一臺物理機上的多例項部署,變得異常困難;而簡單的混合部署,仍然會存在著資源浪費的情況;另外,也無法根據業務規模和寫入速率,定製化分配資源。

從而,一旦資源沒有及時給到位,業務的效能需求將無法滿足;而如果資源給多了,又很難做到高資源利用率。

其次再看看人力成本的投入:

物理機叢集的構建複雜度較高,很容易出現不一致的情況。例如,同一個業務在舊的物理機叢集上,執行是沒問題的,但是遷移到了新物理機叢集,就莫名出問題了;並且每一個獨享叢集的構建,都需要消耗無數的人/天;而當我們辛辛苦苦地將獨享的物理機叢集構建出來之後,會發現後續的維護成本還將隨著叢集數量直線上升。

要知道,即便是支援了自動識別新節點,自動負載均衡的 Druid,也還是會讓運維同學感到壓力山大。而如果是其他不支援這類功能的引擎,則更是雪上加霜。

最終,成本問題便會拉低我們服務的競爭力。

1.2.4 安全問題

最後,我們來看一下安全問題。

在我們的低版本叢集中,是沒有開啟鑑權的,這會導致某一個業務的誤操作,可能會影響其他的業務,造成難以預料的後果。而即便是開啟了鑑權,也會因為沒有物理隔離,仍然無法達到 100% 的安全可靠。

並且,低版本中存在著諸多已知安全漏洞,甚至是類似於 Log4j 這種 0Day 的重大安全漏洞。

共享大叢集的業務數量太多,叢集規模過大,導致升級的阻力和風險極大。這也使得升級的事情一拖再拖,與最新版本相差越來越大,進而還會出現相容性阻礙。也就是說,我們必須要先升級到某一個特定版本,才能升級到最新版本,這會使得升級的複雜度倍增,隨之而來的也是愈發難以評估的操作風險。

另外,也無法享受到新版本的紅利。例如前面提到的重查詢問題,新版本 Druid 中支援了快慢查詢佇列,可以避免個別不合理的重查詢,影響到其他正常的查詢。這也可以一定程度上緩和部分痛點。

以上便是大部分物理機架構下的問題了。接著我們來分析一下,雲原生架構是如何解決這些問題的。

1.3 基於雲原生架構

我們在正式進行雲原生化程式之前,也在核心層面做了大量的工作,但是奈何都難以達到一種“藥到病除”的效果。

同樣,我們也充分進行了調研和測試,對很多技術細節進行權衡和取捨,以找到最適合的方案。並同各個利益方進行了意見的收集,總體而言各方反響強烈,支援我們的架構升級。

俗話說,混亂是階梯。也正是因為共享物理機大叢集的無序和動盪,才給了業務方足夠的動力,來和我們一起完成新架構的落地。

上圖展示了幾個主要的利益相關方,以及其各自的訴求。

本著客戶至上的精神,我們先來了解一下業務方的需求。放在第一位的還是要保障穩定性,並且要能夠具備大促流量峰值的抗壓能力,支援秒級的自動擴容。

隨後是運維方的需求。他們希望能夠保證可觀測性,方便實時、清晰地觀察到各個元件的健康狀態和效能指標;並且,要確保叢集的資源利用率足夠高;還要能夠支援靈活多變的告警策略,做到動態閥值和分級告警等。

最後是核心方的需求。我們期望能夠只專注於核心,完全託管除了 Druid 核心研發以外所有的事宜;要能夠支援 CI/CD 持續整合;以及採用 Docker 映象,代替之前純程式碼的交付模式,提高整體的迭代效率。

而云原生架構包含了高穩定性、高效、低成本和安全穩固等方面的優勢,下文將詳細介紹它是如何滿足各方需求的。

1.3.1 高穩定性

首先是高穩定性。

我們給各個核心業務都建立了獨立的 Druid on K8S 叢集。並且,各個獨享叢集的資源是隔離的,所以從根本上解決了資源搶佔的問題。

而針對每一個業務的特徵,我們也進行了極致的定製最佳化。同時,服務出現故障時,可以做到秒級自動恢復,且使用者無感知。更進一步,我們還在不同的機房構建了 HA 叢集,實現 IDC 級別的高可用,以滿足個別核心業務更為嚴苛的穩定性需求。

除了一般意義上,針對叢集引數配置的定製級最佳化,我們可能還會遇到類似於需要協調壓縮演算法版本的場景。

假設 Druid 上游的業務用了版本 A 的 ZSTD 壓縮演算法,而 Druid 中預設的是版本 B 的,則需要調整並和業務版本保持一致,否則資料無法正常被反序列化。

如果是之前的共享叢集模式,另外新來了一個業務,用的又是版本 C 的,此時就無法協調了。那麼,就需要推動業務,去改造所有的上下游元件,成本將會非常高。

關鍵,還不僅是成本的問題,如果恰巧全鏈路中有一個元件也是共享叢集模式,無法進行調整,那麼整個鏈路都將無法正常打通。

當然,實際會遇到的案例還有很多,這裡暫不一一列舉。

1.3.2 高效

雲原生架構的另一個優勢是高效。

由於我們確保了同一個獨享叢集中,所有專案都是相同的部門或者專案組,使得專案之間的優先順序更容易被評估和排序,從而更容易實現自動流量降級。

並且,因為支援了根據負載情況的自動擴縮容功能,我們不再需要提前和業務溝通,來收集大促流量的增幅,也就不再擔心預估不準確的問題了。

1.3.3 低成本

1)機器資源利用率

隨著 Druid on K8s 叢集規模不斷壯大,在某一個時間點,我們進行了一次橫向對比。結果發現,雲原生架構的叢集機器數量少於舊的物理機叢集,但是承載的業務寫入量卻更多。

在上面這幅柱狀圖中,左邊是物理機架構叢集,右邊是雲原生架構叢集。橘黃色表示資料寫入峰值,紫色表示資料寫入總量。可以看出,無論是峰值還是總量,雲原生架構都是比物理機架構更高的。

因此,可以從這一個側面反映出 Druid 雲原生架構的機器資源利用率更高。

2)人力成本

接下來我們再看看人力成本方面。

因為物理機架構叢集搭建的複雜度比較高,從開始構建,到完全可以作為線上正式環境交付,前後大概需要一個月的時間。即便是後續操作更加熟練,指令碼化程度更高,仍然需要數天的時間。

而云原生架構的叢集搭建,可以透過 CI/CD 的方式,進行一鍵部署,做到分鐘級交付。

上圖 x 軸表示叢集的數量,y 軸表示對應消耗的人/天。黃色的線表示物理機架構,綠色的表示雲原生架構。透過這幅圖可以更加直觀地感受到二者之間的區別。

1.3.4 安全

最後是安全方面的優點。

首先,我們預設會開啟鑑權,保障業務的資料安全。

其次,因為雲原生獨享叢集更加輕量,使得我們更容易跟進最新的 Druid 核心版本。高版本中修復了已知的安全漏洞,會更加安全可靠。

另外,容器化執行的模式,可以實現物理隔離,避免了資源搶佔,很好地控制了故障域。再也不用擔心某一個業務的誤操作波及到其他業務,線上的風險也進一步降低了。

當然,新架構的優勢遠不止這些。並且,優勢疊加之後的放大效應,將我們的服務質量提升到了一個完全不同的高度。

接下來我們總結一下,在雲原生化的過程中又遇到了哪些機遇和挑戰。

1.4 挑戰與機遇

首先,關於挑戰部分:

我們需要引入雲原生的概念,滿足所有利益方的需求,設計出一套全新的雲原生架構;還需要攻克眾多技術難點,從 0 到 1 構建出 K8s 底座;並利用 Docker 映象替代之前純程式碼的迭代方式,以及使用 Helm Chart 完成 Druid 叢集的容器編排和管理;最後,我們需要充分驗證和測試大部分業務場景,並建立標杆使用者。然後,再大面積推廣業務的遷移,並持續進行定製級最佳化。

其二,關於機遇部分:

因為需要構建一整套複雜的雲原生架構,也有機會進一步鍛鍊和提高我們架構的能力;而在實踐和落地 K8s 雲原生的過程,透過解決具體的技術難題,也增強了相關的技術水平;同時,藉助前衛的容器化執行模式,也引領了 Druid 開源社群的雲原生化程式;另外,在逐一完成業務的遷移過程中,也有機會和業務深入瞭解真實的使用場景和痛點,深化了對業務的理解。

相信到這裡,大家已經知道了,我們為什麼要演進到雲原生架構,以及雲原生架構落地過程中遇到的機遇和挑戰。

2. 架構設計

接下來,就讓我們進入架構層面,瞭解一下 Shopee Druid 總體的雲原生架構設計,以及各個元件之間的互動。

2.1 架構總圖

這是我們的架構總圖,主要劃分為了六個層次,分別是業務層、平臺層、視覺化層、引擎層、GitOps 層 和 K8s 層。

最上層的是業務層,主要包含了使用者行為分析、商品推薦、銷售資料分析、品牌分析、網路效能分析、核心指標分析、廣告收益分析、應用軌跡分析、跨境電商分析、內容推薦等。

可以看出,Druid 在 Shopee 內部的應用場景是非常多樣的。

第二層是平臺層,包含了 DataStudio 資料分析、TrinoDB 聯邦查詢引擎、DataHub 資料整合、DataMap 和 Metamart 後設資料管理等。

視覺化層包含了 Druid 自帶的 Web UI、K8s Dashboard、Apache Superset、Grafana,以及業務實現的前端頁面等。

接下來是引擎層面。Druid 下方是 PostgreSQL 作為 Druid 後設資料儲存;HDFS 作為底層儲存,記錄全量的業務資料;ZooKeeper 則作為配置中心。

Druid 右邊是 Kafka 實時資料的寫入和 HDFS 離線資料的匯入;同時,也支援 Spark 資料分析。

最後,右下角是 ElasticSearch 負責日誌的儲存;Druid 透過 Metric System 進行自監控;Grafana 再負責告警。

再來看一下 GitOps 層。我們將整個發版流程劃分為四個部分:開發、測試、預發和生產。

與傳統發版不同的是,我們交付的是 Docker 映象,並使用 Harbor 作為映象倉庫儲存。同時,使用雲原生架構的叢集,透過容器化執行,保障了測試和線上執行環境的一致。再也不用擔心透過了測試環節,結果線上上跑不通的尷尬情形了。

最底層是 kubernetes 叢集。我們在不同的 IDC 機房各自搭建了一套對等的 K8s 叢集,並在 K8s 叢集之上,為各個核心業務構建了獨享的 Druid 叢集。

我們的拆分邏輯是按照部門進行劃分。某一個部門中可能還會有多個專案,而一個專案下,再對應多個 DataSource 表。

舉例來講,假設專案 2 和 3 都是同一個部門下的,如果專案 2 是 metric 監控,專案 3 是計算實際業務資料的,我們就能很容易地進行優先順序排序,在整體資源出現瓶頸的時候,便可以自動地降級專案 2 ,以保障最核心的專案。

架構分層圖剖析完畢。那麼,內部的元件之間又是如何互動與配合的呢?

2.2 元件內部互動圖

先從流量入口看起。

通常,讀寫請求透過視覺化頁面或者後臺程式發起,轉而被 Druid 接收到。

我們可以看到,Druid 內部的各個元件都是多副本的,架構設計上沒有任何的單點問題。這也是為什麼相比於其他很多資料庫,Druid 更容易實現雲原生化。因為 Druid 元件的職責劃分更為到位,很容易對應到 Pod 進行生命週期管理。而每個元件內部的功能又十分內聚,新節點還可以自動識別,並增添到分散式叢集中,使得 HPA 或者 VPA 擴縮容策略的應用,也更加手到擒來。

然後是 ZooKeeper 作為一個配置中心,也負責了 Overlord 和 Coordinator 節點的選主、任務分發的功能。

PostgreSQL 是後設資料儲存引擎,包括 DataSource、Segment 和 Task 的後設資料資訊等。

HDFS 則提供了底層儲存,所有的業務資料都將全量的儲存在 HDFS 叢集中,並根據 Retention Rule 載入到 Historical 資料節點,以加速查詢。

接下來是系統監控部分。

目前有三個層面的指標監控,分別是物理機層面、Service 層面和 Druid Metric 層面:

首先,Prometheus 從物理機的角度進行指標監控,我們就可以知道某一臺機器的負載情況;

其次,MetricBeat 從 Service 服務層面進行指標監控,還可以知道 Druid 中某一個服務的資源使用情況,作為擴縮容的依據;

再者,Druid 自身也有 Metric System 指標監控,還能洞察寫入查詢的負載和效能表現,等更多細節資訊;

當然,還有 Jaeger 可以從另一個視角進行全鏈路分析,以便快速定位到某一個環節的瓶頸,可以進一步提高解決問題的效率。

通常,我們會發現,各個監控組合起來,往往會達到 1+1>2 的效果。但是,我們也需要根據實際情況進行取捨。建議大家逐步地迭代,引入新的監控元件,以得到全新的分析視角,而不能一味地進行堆砌。因為,只有控制好架構的複雜度,才能更有效地降低系統的風險。

最後是 K8s 部分。Ingress 作為流量入口,對 Service 進行了更高維度的抽象,而 Service 會將流量負載均衡之後,重定向到 kube proxy。隨後,再代理轉發到最小排程單元——Pod 上面。

而 Pod 也分為無狀態和有狀態兩種,前者包含了 Router、Broker、Coordinator 和 Overlord 等,後者包括 MiddleManager、Historical、ZooKeeper、PostgreSQL 等。並且,這些有狀態的 Pod 還需要申明 PV 持久卷,方便資料的儲存。而為了避免跨 K8s node 訪問,通常我們會增加節點親和性,提高資料的本地性。

我們還可以看到,有狀態 Pod 組成了 Stateful Set 集合,而無狀態 Pod 組成了 Replica Set 集合。為了便於版本控制和生命週期管理,Replica Set 基礎之上,還抽象出來了 Deployment 的概念。

所有的這些叢集狀態,都被儲存在 K8s master 節點的 Etcd 中。此外,Controller Manager 則會維護叢集的狀態,Scheduler 進行排程,並透過 api server 提供統一入口。

相信到這裡,Druid 雲原生化架構對你而言,已經不再是一個“黑盒”了。

以上便是架構設計的全部內容。接下來的一個小節,將簡單介紹 Shopee 是如何封裝服務,並形成一套完整的解決方案,以應對多樣化場景的業務需求。

3. 落地實踐

圖片源自 Druid

首先,我們會給各個核心業務提供 Druid on K8s 獨享叢集,以保障穩定、安全、高效和低成本。

圖片源自 Grafana

其次,我們還會構建配套的 Grafana on K8S 獨享叢集,並內建一些基礎的監控皮膚,例如 TPS 寫入、QPS 查詢等等。並給到業務 Admin 管理員許可權,方便業務方根據自己的業務場景需求,定製化設計相應的監控皮膚。

另外,部分業務還可能會有二次開發 Grafana 外掛的需求。而我們在獨享模式下,則更容易進行升級和迭代。即便是新外掛出現 Bug,導致 Grafana 故障,也不會影響到其他業務了。

當然,為了支援多樣化的業務場景,我們除了 Grafana,還提供了豐富的視覺化方案:

Turnilo 可以透過“托拉拽”的方式,快速地構建出想要的監控皮膚;

K8s Dashboard 可以實現運維和監控的一體化;

Superset 支援海量的圖表型別和細粒度的許可權管控;

Kibana 則可以對日誌進行視覺化呈現和告警配置。

總有一款能夠很好地契合我們的業務需求。

4. 總結和展望

透過演進到雲原生架構,我們保障了高穩定性、高效能,實現了高效和低成本,極大地提高了服務質量,促進了業務的發展。

我們從架構的維度,解決了核心層面無法處理的,或者是解決起來成本過高的問題。而平時如果你也遇到了類似的困境,不妨跳脫出來,換一個角度來思考,可能也會收穫意想不到的結果。

除了繼續推動架構的升級和核心的完善,我們還會在整合與被整合、開源社群合作,和打造團隊影響等方面,持續發力。

來自 “ Shopee技術團隊 ”, 原文作者:Shopee技術團隊;原文連結:https://copyfuture.com/blogs-details/202209250033516707,如有侵權,請聯絡管理員刪除。

相關文章