從 Clickhouse 到 Apache Doris,慧策電商 SaaS 高併發數據服務的改造實踐

語言: CN / TW / HK

作者介紹: 馬成,慧策 JAVA高級研發工程師

慧策(原旺店通)是一家技術驅動型智能零售服務商,基於雲計算 PaaS、SaaS 模式,以一體化智能零售解決方案,幫助零售企業數字化智能化升級,實現企業規模化發展。憑藉技術、產品、服務優勢,慧策現已成為行業影響力品牌並被零售企業廣泛認可。2021年7月,慧策完成最新一輪 D 輪 3.12 億美元的融資,累計完成四輪逾 4.52 億美元的融資,與諸多國際一流企業、行業頭部企業並列而行,被全球頂尖投資公司看好。

業務需求

慧經營是一款數據分析產品,主要提供經營分析、運營分析、應收對賬三大功能,面向企業的經營、運營、財務三個角色。如圖所示,慧經營上游為常見的電商平台、直播平台以及慧策主要的產品旺店通,這些平台在交易過程中會產生大量的數據,包括收入、成本以及多種費用相關的數據。這些數據都比較分散,如果客户手動進行數據彙總和分析,過程繁瑣、難度較高且展示不夠直觀。而慧經營可以在客户授權下獲取、彙總這些數據,根據客户需求提供經營分析、報表生成、賬單彙總等多項數據分析服務,以幫助客户更好地經營店鋪。

為了能承載上述數據相關的服務,我們在系統架構設計之初需要考慮以下幾點:

  • 低成本:慧經營的付費模式為按需付費,這要求架構必須具有輕量化、易維護的特性。基於這點考慮,首先排除基於 Hadoop 的生態架構,雖然 Hadoop 足夠成熟,但是組件依賴度較高、繁瑣複雜,無論是運維成本還是使用成本都非常高。
  • 高性能:電商數據為慧經營的主要數據來源,數據維度豐富, ETL 複雜度高;另外,在很多使用場景中明細查詢與聚合查詢是並存的,既需要夠快速定位數據範圍,又需要高效的計算能力,因此需要一個強大的 OLAP 引擎來支撐。
  • 合理資源分配:客户規模差異較大,有月單量只有 1 萬的小客户,也有月單量高達千萬級別的大客户,資源合理分配在這樣的場景下異常重要,因此要求 OLAP 引擎可以在不影響用户體驗的前提下,可以根據客户的需求進行資源分配,避免大小查詢資源搶佔帶來的性能問題。
  • 高併發:能夠承受上游平台多來源、高併發的大量數據的複雜 ETL,尤其在 618、雙 11 這樣的業務高峯期,需要具備高效查詢能力的同時,能夠承擔大量的寫入負載。

架構演進

架構 1.0

基於以上考慮,我們在架構 1.0 中引入 Clickhouse 作為 OLAP 引擎。從上圖可看出該架構是一個完全基於後端 Java 以及 Spring 的技術架構,通過 Binlog 同步的方式通過 Canal 和 Kafka 將數據從 MySQL 同步至Clickhouse,來承接前端的各種經營分析報表服務。

架構 1.0 在上線不久後就遭遇了慢查詢問題。在早期客户和數據量較少時,查詢性能尚可滿足客户需求,但是隨着數據量的不斷增大,受限於 MySQL 本身的特性, ETL 效率逐漸低到無法令人接受,尤其在面對大客户場景時,即使命中索引、掃描數據範圍僅在百萬級別,MySQL 應對該場景也已十分吃力。其次,面對激增的數據,ClikHouse 的多表 Join 也遭遇性能問題,由於其幾乎不支持分佈式 Join,儘管提供了 Join 語義、但在使用上對多表 Join 的支撐較弱、複雜的關聯查詢常常會引起 OOM。

在 ClickHouse 單機性能不能支撐業務時,我們考慮擴展搭建集羣以提高性能,而 ClickHouse 分佈式場景需要依賴 Zookeeper,同時需要單獨維護一套分佈式表,這將使運維管理的難度和成本不斷升高。

除此之外,ClickHouse 還有幾個較大的問題,背離了我們最初對架構的選型要求:

  • 不支持高併發,即使一個查詢也會用服務器一半的 CPU。對於三副本的集羣,通常會將 QPS 控制在 100 以下。
  • ClickHouse 的擴容縮容複雜且繁瑣,目前做不到自動在線操作,需要自研工具支持。
  • ClickHouse 的 ReplacingMergeTree 模型必須添加final 關鍵字才能保證嚴格的唯一性,而設置為final後性能會急劇下降。

架構 2.0

我們在架構 2.0 中引入 Apache Doris 作為 OLAP 引擎,並進行了一次整體的架構升級。選擇 Apache Doris 的主要原因有如下幾條:

  • 使用成本低: 只有 FE 和 BE 兩類進程,部署簡單,支持彈性擴縮容,不依賴其他組件,運維成本非常低;同時兼容 MySQL協議 和標準 SQL,開發人員學習難度小,項目整體遷移使用成本也比較低。
  • Join 性能優異: 項目存在很多歷史慢 SQL ,均為多張大表 Join,Apache Doris 良好的 Join 性能給我們提供了一段優化改造的緩衝期。
  • 社區活躍度高: 社區技術氛圍濃厚,且 SelectDB 針對社區有專職的技術支持團隊,在使用過程中遇到問題均能快速得到響應解決。

如上圖所示,我們將原本基於 MySQL 的基礎數據存儲遷移到 Doris 中,同時將大部分的 ETL 遷移至在 Doris 內部進行,MySQL 只用於存儲配置信息。同時引入了 Flink,DolphinScheduler 等組件,成功構建了一套以 Doris 為核心、架構簡潔、運維簡單的數據生產體系。

全新的架構也給我們帶來的顯著的收益:

  • ETL 效率極大提升:慧經營的數據維度豐富、 ETL 複雜度高,得益於 Apache Doris 強大的運算能力及豐富的數據模型,將 ETL 過程放在 Doris 內部進行後效率得到顯著提升;
  • Join 耗時大幅降低:查詢性能同樣得到極大提升,在 SQL 未經過改造的情況下,多表 Join 查詢耗時相較於過去有了大幅降低。
  • 並發表現出色:在業務查詢高峯期可達數千 QPS,而 Apache Doris 面對高併發查詢時始終表現平穩;
  • 存儲空間節省:Apache Doris 的列式存儲引擎實現了最高 1:10 的數據壓縮率,使得存儲成本得到有效降低。
  • 運維便捷:Apache Doris 架構簡單、運維成本低,擴容升級也非常方便,我們前後對 Doris 進行了 3 次升級擴容,基本都在很短的時間內完成。

在使用 Apache Doris 的過程中,我們對 Doris 如何更好地應用也進行了探索,在此期間總結出許多實踐經驗,通過本文分享給大家。

實踐應用

分區分桶優化

在執行 SQL 時,SQL 的資源佔用和分區分桶的大小有着密切的關係,合理的分區分桶將有效提升資源的利用率,同時避免因資源搶佔帶來的性能下降。

因此我們在建立事實表分區時,會根據客户的數據規模提供相匹配的分區方案,比如數據規模較小按年分區、規模大按月分區。在這種分區方式下,可以有效避免小查詢佔用過多資源問題,而大客户的使用體驗也由於細粒度的分區方式得到了提升。

CREATE TABLE DWD_ORDER_... (    
...)    
...
PARTITION BY RANGE(tenant,business_date)
(
PARTITION p1_2022_01_01 VALUES [("1", '2022- 01- 01'), ("1", '2023- 01- 01')),
PARTITION p2_2022_01_01 VALUES [("2", '2022- 01- 01'), ("2", '2022- 07- 01')),
PARTITION p3_2022_01_01 VALUES [("3", '2022- 01- 01'), ("3", '2022- 04- 01')),
PARTITION p4_2022_01_01 VALUES [("4", '2022- 01- 01'), ("4", '2022- 02- 01')),    
...
)

而分桶的設置上我們採用了最新版本中增加的自動分桶推算功能,使用方式非常便捷,不用再去測算和預估每張表的數據量以及對應 Tablet 的增長關係,系統自動幫助我們推算出合理分桶數,提升性能的同時也使得系統資源得到更好利用。

參考文章:一文教你玩轉 Apache Doris 分區分桶新功能|新版本揭祕

多租户和資源隔離方案

在 SaaS 業務場景中,多租户和資源劃分是架構設計過程中必不可少的部分。多租户(Multi-Tenancy )指的是單個集羣可以為多個不同租户提供服務,並且仍可確保各租户間數據隔離性。通過多租户可以保證系統共性的部分被共享,個性的部分被單獨隔離。

對於我們來説,客户規模差異較大,有月單量只有 1 萬的小客户,也有月單量高達千萬級別的大客户,資源的合理分配與隔離在這樣的場景下異常重要。如果為每個租户創建一個數據庫集羣,則集羣規模不可控且造成資源浪費。如果多個租户共享一套數據庫集羣,用户的需求可以相互補償,總體來時可以有很大的資源節約,並且計算規模越大成本越低,而在應對客户查詢時,可以根據客户的數據規模和查詢需求將資源池劃分給各客户。

這裏我們採用了 Apache Doris 的節點資源組來區分大小客户,避免了不同租户以及查詢間的資源搶佔問題。

節點資源組劃分

節點資源劃分指將一個 Doris 集羣內的 BE 節點設置標籤(Tag),標籤相同的 BE 節點組成一個資源組(Resource Group),資源組可以看作是數據存儲和計算的一個管理單元。數據入庫的時候按照資源組配置將數據的副本寫入到不同的資源組中,查詢的時候按照資源組的劃分使用對應資源組上的計算資源對數據進行計算。

例如我們將一個集羣中的 3 個 BE 節點劃分為 2 個資源組,分別為資源組 A 和資源組 B,BE-1 和 BE-2 屬於資源組A,BE-3 屬於資源組B。資源組 A 有 2 副本的數據,資源組 B 有一個副本的數據。那麼當客户 A 在寫入數據和查詢數據的時候會按照資源組配置使用資源組 A 上的存儲和計算資源,當客户 B 寫入數據和查詢數據的時候會按照資源組配置使用資源組 B上 的存儲和計算資源。這樣便能能很好的實現同一個集羣,為不同租户提供不同的負載能力。

具體操作如下:

  1. 設置標籤:

為 BE 節點設置標籤。假設當前 Doris 集羣有 3 個 BE 節點。分別為 host[1-3]。在初始情況下,所有節點都屬於一個默認資源組(Default)。

我們可以使用以下命令將這6個節點劃分成3個資源組:group_agroup_b

  alter system modify backend "host1:9050" set ("tag.location" = "group_a");
  alter system modify backend "host2:9050" set ("tag.location" = "group_a");
  alter system modify backend "host3:9050" set ("tag.location" = "group_b");

這裏我們將 host[1-2]組成資源組 group_ahost[3]組成資源組group_b

  1. 設置資源組數據分配策略

副本分佈策略定義:資源組劃分好後,我們可以將客户數據的不同副本分佈在不同資源組內。假設一張用户表 UserTable。我們希望資源組 A 內存放 2 個副本,資源組 B 存放 1 分副本

  create table UserTable (k1 int, k2 int)
  distributed by hash(k1) buckets 1
  properties(
      "replication_allocation"="tag.location.group_a:2, tag.location.group_b:1"
  )

資源組綁定:通過設置客户的資源組使用權限,來限制某一客户的數據導入和查詢只能使用指定資源組中的節點來執行。

  set property for 'user_a' 'resource_tags.location' = 'group_a';
  set property for 'user_b' 'resource_tags.location' = 'group_b';

這裏將 user_agroup_a資源綁定,user_bgroup_b 資源綁定。綁定完成後,user_a 在發起對 UserTable 表的查詢時,只會訪問 group_a資源組內節點上的數據副本,並且查詢僅會使用 group_a 資源組內的節點計算資源。

通過 Apache Doris 提供多租户和資源隔離方案,能夠將集羣資源更合理的分配給各 客户 ,可以讓多租户在同一 Doris 集羣內進行數據操作時,減少相互之間的干擾,同時達到資源成本的有效降低。

高併發場景的支持

高併發也通常是 SaaS 服務場景面臨的挑戰,一方面是慧經營已經為眾多客户提供了分析服務,需要去同時承載大規模客户的併發查詢和訪問,另一方面,在每日早晚業務高峯期也會面臨更多客户的同時在線,通常 QPS 最高可達數千,而這也是 Apache Doris 表現得非常出色的地方。在上游 Flink 高頻寫入的同時,Apache Doris 表現極為平穩,隨着查詢併發的提升,查詢耗時相較於平時幾乎沒有太大的波動。

應對高併發場景的關鍵在於利用好分區分桶裁剪、索引、緩存等系統機制來減少底層數據掃描。在 Doris 中會將數據表的前幾列作為排序鍵來構建前綴索引,同時還有 ZoneMap 以及 Bloom Filter 等索引。在執行查詢時,通過分區分桶裁剪以及索引可以快速過濾到不在查詢範圍內的數據,減少 CPU 和 IO 的壓力。

在與社區的溝通中還了解到,即將發佈的新版本還將進一步提升併發支持,引入行存儲格式、短路徑優化以及預處理語句等來實現上萬 QPS 的超高併發,可以滿足更高併發要求的 Data Serving 場景,這也是我們十分期待的功能。

數據生命週期管理

我們自研了一套生命週期管理工具,基於資源的分配,可以提供客户級定製化冷卻策略。根據各個業務場景的客户需求及付費情況,提供不同長度的數據保留策略,同時得益於 Apache Doris 提供的 OutFile 功能,實現刪除或者導出 S3 兩種冷卻方式,當我們再次需要這些數據時,可通過 Load 的方式從 S3 導回數據。

除了通過 OutFile 將數據文件導出至 S3 以外,社區在後續版本中還將提供分區級別的冷熱數據分離策略。當前我們數據分區都是基於日期範圍來設置,因此可以利用時間分區進行冷熱數據的劃分。通過配置分區級別的 Freeze Time,轉冷後的歷史數據可以自動下沉至成本更低的對象存儲上。在面對歷史冷數據的查詢時,Doris 會自動拉取對象存儲上的數據並在本地 Cache 以加速查詢,而無需執行導入操作。通過冷熱數據分離以及冷數據 Cache,既能保證最大程度的資源節省,也能保證冷數據的查詢性能不受影響。

總結規劃

收益

引入 Apache Doris 之後,新架構的整體查詢響應速度都有了較大的提升, 存儲成本顯著降低,後續我們將繼續探索 Doris 新特性,進一步實現降本增效。

規劃

  • 目前正在強化 Apache Doris 的元數據管理建設,我們希望通過該服務能更好的協助開發人員使用 Doris,包括數據血緣追蹤等;
  • 嘗試使用更多 Apache Doris 的新功能,在 1.2.0 版本中新增的 Java UDF 功能可以極大降低數據出入庫的頻率,更便捷地在 Doris 內部進行數據 ETL,這一功能我們正在嘗試使用中;
  • 探索更好的資源分配方案,包括 SQL 代理以及嘗試 Doris 後續提供的 Spill 功能,以更好進行資源分配應用。

最後,再次感謝 Apache Doris 社區和 SelectDB 技術團隊在我們使用過程中提供的許多指導和幫助,提出問題都會及時的響應並儘快協助解決,未來我們也希望深度參與到社區建設中,為社區的發展出一份力。

「其他文章」