ClickHouse 是2016年開(kāi)源的用于實(shí)時(shí)數(shù)據(jù)分析的一款高性能列式分布式數(shù)據(jù)庫(kù),支持向量化計(jì)算引擎、多核并行計(jì)算、高壓縮比等功能,在分析型數(shù)據(jù)庫(kù)中單表查詢速度是最快的。2020年開(kāi)始在滴滴內(nèi)部大規(guī)模地推廣和應(yīng)用,服務(wù)網(wǎng)約車和日志檢索等核心平臺(tái)和業(yè)務(wù)。本文主要介紹滴滴日志檢索場(chǎng)景從 ES 遷移到 CK 的技術(shù)探索。
背景
此前,滴滴日志主要存儲(chǔ)于 ES 中。然而,ES 的分詞、倒排和正排等功能導(dǎo)致其寫(xiě)入吞吐量存在明顯瓶頸。此外,ES 需要存儲(chǔ)原始文本、倒排索引和正排索引,這增加了存儲(chǔ)成本,并對(duì)內(nèi)存有較高要求。隨著滴滴數(shù)據(jù)量的不斷增長(zhǎng),ES 的性能已無(wú)法滿足當(dāng)前需求。
在追求降低成本和提高效率的背景下,我們開(kāi)始尋求新的存儲(chǔ)解決方案。經(jīng)過(guò)研究,我們決定采用 CK 作為滴滴內(nèi)部日志的存儲(chǔ)支持。據(jù)了解,京東、攜程、B站等多家公司在業(yè)界的實(shí)踐中也在嘗試用 CK 構(gòu)建日志存儲(chǔ)系統(tǒng)。
挑戰(zhàn)
面臨的挑戰(zhàn)主要來(lái)自下面三個(gè)方面:
數(shù)據(jù)量大:每天會(huì)產(chǎn)生 PB 級(jí)別的日志數(shù)據(jù),存儲(chǔ)系統(tǒng)需要穩(wěn)定地支撐 PB 級(jí)數(shù)據(jù)的實(shí)時(shí)寫(xiě)入和存儲(chǔ)。
查詢場(chǎng)景多:在一個(gè)時(shí)間段內(nèi)的等值查詢、模糊查詢及排序場(chǎng)景等,查詢需要掃描的數(shù)據(jù)量較大且查詢都需要在秒級(jí)返回。
QPS 高:在 PB 級(jí)的數(shù)據(jù)量下,對(duì) Trace 查詢同時(shí)要滿足高 QPS 的要求。
為什么選 Clickhouse
大數(shù)據(jù)量:CK 的分布式架構(gòu)支持動(dòng)態(tài)擴(kuò)縮容,可支撐海量數(shù)據(jù)存儲(chǔ)。
寫(xiě)入性能:CK 的 MergeTree 表的寫(xiě)入速度在200MB/s,具有很高吞吐,寫(xiě)入基本沒(méi)有瓶頸。
查詢性能:CK 支持分區(qū)索引和排序索引,具有很高的檢索效率,單機(jī)每秒可掃描數(shù)百萬(wàn)行的數(shù)據(jù)。
存儲(chǔ)成本:CK 基于列式存儲(chǔ),數(shù)據(jù)壓縮比很高,同時(shí)基于HDFS做冷熱分離,能夠進(jìn)一步地降低存儲(chǔ)成本。
架構(gòu)升級(jí)
舊的存儲(chǔ)架構(gòu)下需要將日志數(shù)據(jù)雙寫(xiě)到 ES 和 HDFS 兩個(gè)存儲(chǔ)上,由ES提供實(shí)時(shí)的查詢,Spark 來(lái)分析 HDFS 上的數(shù)據(jù)。這種設(shè)計(jì)要求用戶維護(hù)兩條獨(dú)立的寫(xiě)入鏈路,導(dǎo)致資源消耗翻倍,且操作復(fù)雜性增加。
在新升級(jí)的存儲(chǔ)架構(gòu)中,CK 取代了 ES 的角色,分別設(shè)有 Log 集群和 Trace 集群。Log 集群專門(mén)存儲(chǔ)明細(xì)日志數(shù)據(jù),而 Trace 集群則專注于存儲(chǔ) trace 數(shù)據(jù)。這兩個(gè)集群在物理上相互隔離,有效避免了 log 的高消耗查詢(如 like 查詢)對(duì) trace 的高 QPS 查詢產(chǎn)生干擾。此外,獨(dú)立的 Trace 集群有助于防止trace數(shù)據(jù)過(guò)度分散。
日志數(shù)據(jù)通過(guò) Flink 直接寫(xiě)入 Log 集群,并通過(guò) Trace 物化視圖從 log 中提取 trace 數(shù)據(jù),然后利用分布式表的異步寫(xiě)入功能同步至 Trace 集群。這一過(guò)程不僅實(shí)現(xiàn)了 log 與 trace 數(shù)據(jù)的分離,還允許 Log 集群的后臺(tái)線程定期將冷數(shù)據(jù)同步到 HDFS 中。
新架構(gòu)僅涉及單一寫(xiě)入鏈路,所有關(guān)于 log 數(shù)據(jù)冷存儲(chǔ)至 HDFS 以及 log 與 trace 分離的處理均由 CK 完成,從而為用戶屏蔽了底層細(xì)節(jié),簡(jiǎn)化了操作流程。
考慮到成本和日志數(shù)據(jù)特點(diǎn),Log 集群和 Trace 集群均采用單副本部署模式。其中,最大的 Log 集群有300多個(gè)節(jié)點(diǎn),Trace 集群有40多個(gè)節(jié)點(diǎn)。
存儲(chǔ)設(shè)計(jì)
存儲(chǔ)設(shè)計(jì)是提升性能最關(guān)鍵的部分,只有經(jīng)過(guò)優(yōu)化的存儲(chǔ)設(shè)計(jì)才能充分發(fā)揮 CK 強(qiáng)大的檢索性能。借鑒時(shí)序數(shù)據(jù)庫(kù)的理念,我們將 logTime 調(diào)整為以小時(shí)為單位進(jìn)行取整,并在存儲(chǔ)過(guò)程中按照小時(shí)順序排列數(shù)據(jù)。這樣,在進(jìn)行其他排序鍵查詢時(shí),可以快速定位到所需的數(shù)據(jù)塊。例如,查詢一個(gè)小時(shí)內(nèi)數(shù)據(jù)時(shí),最多只需讀取兩個(gè)索引塊,這對(duì)于處理海量日志檢索至關(guān)重要。
以下是我們根據(jù)日志查詢特性和 CK 執(zhí)行邏輯制定的存儲(chǔ)設(shè)計(jì)方案,包括 Log 表、Trace 表和 Trace 索引表:
Log 表
Log 表旨在為明細(xì)日志提供存儲(chǔ)和查詢服務(wù),它位于 Log 集群中,并由 Flink 直接從 Pulsar 消費(fèi)數(shù)據(jù)后寫(xiě)入。每個(gè)日志服務(wù)都對(duì)應(yīng)一張 Log 表,因此整個(gè) Log 集群可能包含數(shù)千張 Log 表。其中,最大的表每天可能會(huì)生成 PB 級(jí)別的數(shù)據(jù)。鑒于 Log 集群面臨表數(shù)量眾多、單表數(shù)據(jù)量大以及需要進(jìn)行冷熱數(shù)據(jù)分離等挑戰(zhàn),以下是針對(duì) Log 表的設(shè)計(jì)思路:
CREATE TABLE ck_bamai_stream.cn_bmauto_local ( `logTime` Int64 DEFAULT 0, -- log打印的時(shí)間 `logTimeHour` DateTime MATERIALIZED toStartOfHour(toDateTime(logTime / 1000)), -- 將logTime向小時(shí)取整 `odinLeaf` String DEFAULT '', `uri` LowCardinality(String) DEFAULT '', `traceid` String DEFAULT '', `cspanid` String DEFAULT '', `dltag` String DEFAULT '', `spanid` String DEFAULT '', `message` String DEFAULT '', `otherColumn` Map, `_sys_insert_time` DateTime MATERIALIZED now() ) ENGINE = MergeTree PARTITION BY toYYYYMMDD(logTimeHour) ORDER BY (logTimeHour,odinLeaf,uri,traceid) TTL _sys_insert_time + toIntervalDay(7),_sys_insert_time + toIntervalDay(3) TO VOLUME 'hdfs' SETTINGSindex_granularity=8192,min_bytes_for_wide_part=31457280
分區(qū)鍵:根據(jù)查詢特點(diǎn),幾乎所有的 sql 都只會(huì)查1小時(shí)的數(shù)據(jù),但這里只能按天分區(qū),小時(shí)分區(qū)導(dǎo)致 Part 過(guò)多及 HDFS 小文件過(guò)多的問(wèn)題。
排序鍵:為了快速定位到某一個(gè)小時(shí)的數(shù)據(jù),基于 logTime 向小時(shí)取整物化了一個(gè)新的字段 logTimeHour,將 logTimeHour 作為第一排序鍵,這樣就能將數(shù)據(jù)范圍鎖定在小時(shí)級(jí)別,由于大部分查詢都會(huì)指定上 odinLeaf、uri、traceid,依據(jù)基數(shù)從小到大分別將其作為第二、三、四排序鍵,這樣查詢某個(gè) traceid 的數(shù)據(jù)只需要讀取少量的索引塊,經(jīng)過(guò)上述的設(shè)計(jì)所有的等值查詢都能達(dá)到毫秒級(jí)。
Map 列:引入了 Map 類型,實(shí)現(xiàn)動(dòng)態(tài)的 Scheme,將不需要用來(lái)過(guò)濾的列統(tǒng)統(tǒng)放入 Map 中,這樣能有效減少 Part 的文件數(shù),避免 HDFS 上出現(xiàn)大量小文件。
Trace 表
Trace 表是用來(lái)提供 trace 相關(guān)的查詢,這類查詢對(duì) QPS 要求很高,創(chuàng)建在 Trace 集群。數(shù)據(jù)來(lái)源于從 Log 表中提取的 trace 記錄。Trace 表只會(huì)有一張,所有的 Log 表都會(huì)將 trace 記錄提取到這張 Trace 表,實(shí)現(xiàn)的方式是 Log 表通過(guò)物化視圖觸發(fā)器跨集群將數(shù)據(jù)寫(xiě)到 Trace 表中。
Trace 表的難點(diǎn)在于查詢速度快且 QPS 高,以下是 Trace 表的設(shè)計(jì)思路:
CREATE TABLE ck_bamai_stream.trace_view ( `traceid` String, `spanid` String, `clientHost` String, `logTimeHour` DateTime, `cspanid` AggregateFunction(groupUniqArray, String), `appName` SimpleAggregateFunction(any, String), `logTimeMin` SimpleAggregateFunction(min, Int64), `logTimeMax` SimpleAggregateFunction(max, Int64), `dltag` AggregateFunction(groupUniqArray, String), `uri` AggregateFunction(groupUniqArray, String), `errno` AggregateFunction(groupUniqArray, String), `odinLeaf` SimpleAggregateFunction(any, String), `extractLevel` SimpleAggregateFunction(any, String) ) ENGINE = AggregatingMergeTree PARTITION BY toYYYYMMDD(logTimeHour) ORDER BY (logTimeHour, traceid, spanid, clientHost) TTL logTimeHour + toIntervalDay(7) SETTINGS index_granularity = 1024
AggregatingMergeTree:Trace 表采用了聚合表引擎,會(huì)按 traceid 進(jìn)行聚合,能很大程度的聚合 trace 數(shù)據(jù),壓縮比在5:1,能極大地提升 Trace 表的檢索速度。
分區(qū)鍵和排序鍵:與 Log 的設(shè)計(jì)類似。
index_granularity:這個(gè)參數(shù)是用來(lái)控制稀疏索引的粒度,默認(rèn)是8192,減小這個(gè)參數(shù)是為了減少數(shù)據(jù)塊中無(wú)效的數(shù)據(jù)掃描,加快 traceid 的檢索速度。
Trace 索引表
Trace 索引表的主要作用是加快 order_id、driver_id、driver_phone 等字段查詢 traceid 的速度。為此,我們給需要加速的字段創(chuàng)建了一個(gè)聚合物化視圖,以提高查詢速度。數(shù)據(jù)則是通過(guò)為 Log 表創(chuàng)建相應(yīng)的物化視圖觸發(fā)器,將數(shù)據(jù)提取到 Trace 索引表中。
以下是建立 Trace 索引表的語(yǔ)句:
CREATE TABLE orderid_traceid_index_view ( `order_id` String, `traceid` String, `logTimeHour` DateTime ) ENGINE = AggregatingMergeTree PARTITION BY logTimeHour ORDER BY (order_id, traceid) TTL logTimeHour + toIntervalDay(7) SETTINGS index_granularity = 1024
存儲(chǔ)設(shè)計(jì)的核心目標(biāo)是提升查詢性能。接下來(lái),我將介紹從 ES 遷移至 CK 過(guò)程中,在這一架構(gòu)下所面臨的穩(wěn)定性問(wèn)題及其解決方法。
穩(wěn)定性之路
支撐日志場(chǎng)景對(duì) CK 來(lái)說(shuō)是非常大的挑戰(zhàn),面臨龐大的寫(xiě)入流量及超大集群規(guī)模,經(jīng)過(guò)一年的建設(shè),我們能夠穩(wěn)定的支撐重點(diǎn)節(jié)假日的流量高峰,下面的篇幅主要是介紹了在支撐日志場(chǎng)景過(guò)程中,遇到的一些問(wèn)題。
大集群小表數(shù)據(jù)碎片化問(wèn)題
在 Log 集群中,90%的 Log 表流量低于10MB/s。若將所有表的數(shù)據(jù)都寫(xiě)入數(shù)百個(gè)節(jié)點(diǎn),會(huì)導(dǎo)致大量小表數(shù)據(jù)碎片化問(wèn)題。這不僅影響查詢性能,還會(huì)對(duì)整個(gè)集群性能產(chǎn)生負(fù)面影響,并為冷數(shù)據(jù)存儲(chǔ)到 HDFS 帶來(lái)大量小文件問(wèn)題。
為解決大規(guī)模集群帶來(lái)的問(wèn)題,我們根據(jù)表的流量大小動(dòng)態(tài)分配寫(xiě)入節(jié)點(diǎn)。每個(gè)表分配的寫(xiě)入節(jié)點(diǎn)數(shù)量介于2到集群最大節(jié)點(diǎn)數(shù)之間,均勻分布在整個(gè)集群中。Flink 通過(guò)接口獲取表的寫(xiě)入節(jié)點(diǎn)列表,并將數(shù)據(jù)寫(xiě)入相應(yīng)的 CK 節(jié)點(diǎn),有效解決了大規(guī)模集群數(shù)據(jù)分散的問(wèn)題。
寫(xiě)入限流及寫(xiě)入性能提升
在滴滴日志場(chǎng)景中,晚高峰和節(jié)假日的流量往往會(huì)大幅增加。為避免高峰期流量過(guò)大導(dǎo)致集群崩潰,我們?cè)?Flink 上實(shí)現(xiàn)了寫(xiě)入限流功能。該功能可動(dòng)態(tài)調(diào)整每張表寫(xiě)入集群的流量大小。當(dāng)流量超過(guò)集群上限時(shí),我們可以迅速降低非關(guān)鍵表的寫(xiě)入流量,減輕集群壓力,確保重保表的寫(xiě)入和查詢不受影響。
同時(shí)為了提升把脈的寫(xiě)入性能,我們基于 CK 原生 TCP 協(xié)議開(kāi)發(fā)了 Native-connector。相比于 HTTP 協(xié)議,Native-connector 的網(wǎng)絡(luò)開(kāi)銷更小。此外,我們還自定義了數(shù)據(jù)類型的序列化機(jī)制,使其比之前的 Parquet 類型更高效。啟用 Native-connector 后,寫(xiě)入延遲率從之前的20%降至5%,整體寫(xiě)入性能提升了1.2倍。
HDFS 冷熱分離的性能問(wèn)題
用 HDFS 來(lái)存儲(chǔ)冷數(shù)據(jù),在使用的過(guò)程中出現(xiàn)以下問(wèn)題:
服務(wù)重啟變得特別慢且 Sys cpu 被打滿,原因是在服務(wù)重啟的過(guò)程中需要并發(fā)的加載 HDFS 上 Part 的元數(shù)據(jù),而 libhdfs3 庫(kù)并發(fā)讀 HDFS 的性能非常差,每當(dāng)讀到文件末尾都會(huì)拋出異常打印堆棧,產(chǎn)生了大量的系統(tǒng)調(diào)用。
當(dāng)寫(xiě)入歷史分區(qū)的數(shù)據(jù)時(shí),數(shù)據(jù)不會(huì)落盤(pán),而是直接往 HDFS 上寫(xiě),寫(xiě)入性能很差,并且直接寫(xiě)到 HDFS 的數(shù)據(jù)還需要拉回本地 merge,進(jìn)一步降低了 merge 的性能。
本地的 Part 路徑和 HDFS 的路徑是通過(guò) uuid 來(lái)映射的,所有表的數(shù)據(jù)都是存儲(chǔ)在 HDFS 的同一路徑下,導(dǎo)致達(dá)到了 HDFS 目錄文件數(shù) 100w 的限制。
HDFS 上的 Part 文件路徑映射關(guān)系是存儲(chǔ)在本地的,如果出現(xiàn)節(jié)點(diǎn)故障,那么文件路徑映射關(guān)系將會(huì)丟失,HDFS 上的數(shù)據(jù)丟失且無(wú)法被刪除。
為此我們對(duì) HDFS 冷熱分離功能進(jìn)行了比較大的改造來(lái)解決上面的問(wèn)題,解決 libhdfs3 庫(kù)并發(fā)讀 HDFS 的問(wèn)題并在本地緩存 HDFS 的 Part 元數(shù)據(jù)文件,服務(wù)的啟動(dòng)速度由原來(lái)的1小時(shí)到1分鐘。
同時(shí)禁止歷史數(shù)據(jù)直接寫(xiě) HDFS ,必須先寫(xiě)本地,merge 之后再上傳到 HDFS ,最后對(duì) HDFS 的存儲(chǔ)路徑進(jìn)行改造。由原來(lái)數(shù)據(jù)只存儲(chǔ)在一個(gè)目錄下改為按cluster/shard/database/table/ 進(jìn)行劃分,并按表級(jí)別備份本地的路徑映射關(guān)系到 HDFS。這樣一來(lái),當(dāng)節(jié)點(diǎn)故障時(shí),可以通過(guò)該備份恢復(fù) HDFS 的數(shù)據(jù)。
收益
在日志場(chǎng)景中,我們已經(jīng)成功完成了從 ES 到 CK 的遷移。目前,CK 的日志集群規(guī)模已超過(guò)400個(gè)物理節(jié)點(diǎn),寫(xiě)入峰值流量達(dá)到40+GB/s,每日查詢量約為1500萬(wàn)次,支持的 QPS 峰值約為200。相較于 ES,CK 的機(jī)器成本降低了30%。
查詢速度相比 ES 提高了約4倍。下圖展示了 bamailog 集群和 bamaitrace 集群的 P99 查詢耗時(shí)情況,基本都在1秒以內(nèi)。
總結(jié)
將日志從 ES 遷移至 CK 不僅可以顯著降低存儲(chǔ)成本,還能提供更快的查詢體驗(yàn)。經(jīng)過(guò)一年多的建設(shè)和優(yōu)化,系統(tǒng)的穩(wěn)定性和性能都有了顯著提升。然而,在處理模糊查詢時(shí),集群的資源消耗仍然較大。未來(lái),我們將繼續(xù)探索二級(jí)索引、zstd 壓縮以及存算分離等技術(shù)手段,以進(jìn)一步提升日志檢索性能。
審核編輯:黃飛
-
TCP
+關(guān)注
關(guān)注
8文章
1400瀏覽量
80634 -
存儲(chǔ)系統(tǒng)
+關(guān)注
關(guān)注
2文章
423瀏覽量
41299 -
HDFS
+關(guān)注
關(guān)注
1文章
31瀏覽量
9856 -
大數(shù)據(jù)
+關(guān)注
關(guān)注
64文章
8953瀏覽量
139792
原文標(biāo)題:滴滴基于 Clickhouse 構(gòu)建新一代日志存儲(chǔ)系統(tǒng)
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
網(wǎng)絡(luò)存儲(chǔ)系統(tǒng)可生存性量化評(píng)估
Centos7下如何搭建ClickHouse列式存儲(chǔ)數(shù)據(jù)庫(kù)
如何構(gòu)建一個(gè)PCI-E架構(gòu)的實(shí)時(shí)海量存儲(chǔ)系統(tǒng)?
什么是云存儲(chǔ)?云存儲(chǔ)系統(tǒng)的結(jié)構(gòu)是如何構(gòu)成的?
存儲(chǔ)系統(tǒng)的層次結(jié)構(gòu)
存儲(chǔ)系統(tǒng)的層次結(jié)構(gòu)是怎樣的?
基于FPGA的微型數(shù)字存儲(chǔ)系統(tǒng)設(shè)計(jì)

公有云存儲(chǔ)系統(tǒng)性能評(píng)測(cè)方法研究

富士通推出全新企業(yè)級(jí)全閃存儲(chǔ)系統(tǒng)系列產(chǎn)品:性能較上一代提升了30%以上
如何使用iSCSI技術(shù)構(gòu)建IP SAN網(wǎng)絡(luò)存儲(chǔ)系統(tǒng)的方法概述

新華三推出全新一代存儲(chǔ)系統(tǒng)
新華三推出全新一代數(shù)據(jù)存儲(chǔ)系統(tǒng)Primera
浪潮信息以新一代存儲(chǔ)核心構(gòu)建新一代集中式和分布式存儲(chǔ)平臺(tái)

日志結(jié)構(gòu)存儲(chǔ)下數(shù)據(jù)放置的方法淺析

評(píng)論