使用 ClickHouse 做日誌分析

這是我們在 Monitorama 2022 上發表的演講的改編稿。您可以在此處找到包含演講者筆記的幻燈片和此處的視頻。

當 Cloudflare 的請求拋出錯誤時,信息會記錄在我們的 requests_error 管道中。錯誤日誌用於幫助解決特定於客戶或網絡範圍的問題。

我們,站點可靠性工程師 (SRE),負責管理日誌平臺。我們已經運行 Elasticsearch 集羣很多年了,這些年來日誌量急劇增加。隨着日誌量的增加,我們開始面臨一些問題。查詢性能慢、資源消耗高等。我們的目標是通過提高查詢性能並提供經濟高效的日誌存儲解決方案來改善日誌消費者的體驗。這篇博文討論了日誌記錄管道的挑戰以及我們如何設計新架構以使其更快且更具成本效益。

在我們深入探討維護日誌管道的挑戰之前,讓我們先了解一下日誌的特徵。

日誌的特徵

不可預測:當今世界,微服務數量衆多,集中式日誌系統將收到的日誌量非常難以預測。日誌體量估算如此困難的原因有多種。主要是因爲新應用程序不斷部署到生產中,現有應用程序會自動擴展或縮小以滿足業務需求,或者有時應用程序所有者啓用調試日誌級別並忘記將其關閉。

上下文:對於調試問題,通常需要上下文信息,即事件發生之前和之後的日誌。單個日誌行幾乎沒有幫助,通常,是一組日誌行有助於構建上下文。此外,我們經常需要將多個應用程序的日誌關聯起來以繪製全貌。因此,必須保留日誌在數據源處填充的順序。

寫入密集型:任何集中式日誌系統都是寫入密集型的。超過 99% 的已寫入日誌從未被讀取。它們佔用空間一段時間,並最終被保留策略清除。剩下的不到 1% 的被讀取的日誌非常重要,我們不能錯過它們。

日誌管道

與大多數其他公司一樣,我們的日誌記錄管道由生產者、路由轉發器、隊列、消費者和存儲組成。

在 Cloudflare 全球網絡上運行的應用程序(生產者)生成日誌。這些日誌以 Cap’n Proto 序列化格式在本地寫入。 Shipper(內部解決方案)通過流將 Cap’n Proto 序列化日誌推送到 Kafka(隊列)進行處理。我們運行 Logstash(消費者),它從 Kafka 消費並將日誌寫入 ElasticSearch(數據存儲)。然後使用 Kibana 或 Grafana 可視化數據。我們在 Kibana 和 Grafana 中內置了多個儀表板來可視化數據。

Cloudflare 的 Elasticsearch 瓶頸

在 Cloudflare,我們多年來一直運行 Elasticsearch 集羣。多年來,日誌量急劇增加,在優化 Elasticsearch 集羣以處理此類量時,我們發現了一些限制。

Mapping 爆炸

Mapping 爆炸是 Elasticsearch 衆所周知的侷限性之一。 Elasticsearch 維護一個映射,決定如何存儲和索引新文檔及其字段。當此映射中的鍵太多時,可能會佔用大量內存,從而導致頻繁的垃圾回收。防止這種情況的一種方法是使 schema 嚴格,這意味着任何不遵循此嚴格 schema 的日誌行最終都會被刪除。另一種方法是使其成爲半嚴格的,這意味着不屬於此映射的任何字段都將不可搜索。

多租戶支持

Elasticsearch 沒有很好的多租戶支持。一個壞用戶很容易影響集羣性能。無法限制查詢可以讀取的文檔或索引的最大數量或 Elasticsearch 查詢可以佔用的內存量。錯誤的查詢很容易降低集羣性能,即使查詢完成後,它仍然會留下影響。

集羣維護工作

管理 Elasticsearch 集羣並不容易,尤其是多租戶集羣。一旦集羣降級,就需要花費大量時間才能使集羣恢復到完全健康的狀態。在 Elasticsearch 中,更新索引模板意味着重新索引數據,這是一個相當大的開銷。我們使用冷熱分層存儲,即最近的數據存儲在 SSD 中,較舊的數據存儲在機械硬盤中。雖然 Elasticsearch 每天都會將數據從熱存儲移動到冷存儲,但它會影響集羣的讀寫性能。

垃圾回收

Elasticsearch 使用 Java 開發並在 Java 虛擬機 (JVM) 上運行。它執行垃圾收集以回收由程序分配但不再引用的內存。Elasticsearch 需要垃圾收集調整。最新的 JVM 中默認的垃圾回收是 G1GC。我們嘗試了其他 GC,例如 ZGC,這有助於減少 GC 暫停,但在讀寫吞吐量方面並沒有給我們帶來太多性能優勢。

Elasticsearch 是一個很好的全文搜索工具,這些限制對於小型集羣來說並不重要,但在 Cloudflare 中,我們每秒處理超過 35 到 4500 萬個 HTTP 請求,其中每秒有超過 500K-800K 的請求失敗。這些失敗可能是由於不正確的請求、源服務器錯誤、用戶配置錯誤、網絡問題和各種其他原因造成的。

我們的客戶支持團隊使用這些錯誤日誌作爲定位客戶問題的起點。錯誤日誌包含有關 HTTP 請求所經過的各種 Cloudflare 產品的許多字段元數據。我們將這些錯誤日誌存儲在 Elasticsearch 中。我們對它們進行了大量採樣,因爲存儲所有內容需要花費數百 TB 的空間,超出了我們的資源分配預算。此外,基於它構建的儀表板非常慢,因爲它們需要對各個字段進行大量聚合。根據調試要求,我們需要將這些日誌保留幾周。

建議的解決方案

我們希望完全取消採樣,即存儲保留期內的每條日誌行,爲如此龐大的數據量提供快速查詢支持,並在不增加成本的情況下實現這一切。爲了解決所有這些問題,我們決定進行概念驗證,看看是否可以使用 ClickHouse 來滿足我們的要求。

Cloudflare 是 ClickHouse 的早期採用者,我們多年來一直在管理 ClickHouse 集羣。我們已經擁有許多內部工具和庫,用於將數據插入 ClickHouse,這使我們可以輕鬆進行概念驗證。讓我們看一下 ClickHouse 的一些功能,這些功能使其非常適合存儲日誌,並使我們能夠構建新的日誌管道。

ClickHouse 是一個面向列的數據庫,這意味着與特定列相關的所有數據在物理上彼此相鄰存儲。即使在普通商用硬件上,這種數據佈局也有助於快速順序掃描。這使我們能夠從老一代硬件中獲得最大性能。

ClickHouse 專爲分析工作負載而設計,數據可以有很多列。我們能夠設計具有大量列的新 ClickHouse 表,而不會犧牲性能。

ClickHouse 索引的工作方式與關係數據庫中的索引不同。在關係數據庫中,主索引非常密集,並且每個錶行包含一個條目。因此,如果表中有 100 萬行,主索引也將有 100 萬個條目。而在 ClickHouse 中,索引是稀疏的,這意味着每幾千行只有一個索引條目。ClickHouse 索引使我們能夠動態添加新索引。

ClickHouse 默認使用 LZ4 壓縮所有內容。高效的壓縮不僅有助於最大限度地減少存儲需求,還可以讓 ClickHouse 有效地使用頁面緩存。

ClickHouse 的一項很酷的功能是可以按列配置壓縮編解碼器。我們決定爲所有列保留默認的 LZ4 壓縮。我們對 DateTime 列使用了 Double-Delta,對 Float 列使用了 Gorilla,對固定大小的 String 列使用了 LowCardinality。

ClickHouse 是線性可擴展的;也就是說,寫入可以通過添加新分片來擴展,讀取可以通過添加新副本來擴展。ClickHouse 集羣中的每個節點都是相同的。沒有任何特殊節點有助於輕鬆擴展集羣。

讓我們看一下我們用來提供更快的讀 / 寫吞吐量和更好的日誌數據壓縮的一些優化。

Inserter

擁有高效的插入器與擁有高效的數據存儲一樣重要。在 Cloudflare,我們一直在運行相當多的分析管道,在編寫新的插入器時我們借用了大部分概念。我們使用 Cap’n Proto 消息作爲傳輸數據格式,因爲它提供快速的數據編碼和解碼。擴展插入器很容易,可以通過添加更多 Kafka 分區並生成新的插入器 Pod 來完成。

Batch Size

將數據插入 ClickHouse 時的關鍵性能因素之一是批量大小。當批量較小時,ClickHouse 會創建許多小分區,然後將其合併爲更大的分區。因此,較小的批量大小會給 ClickHouse 在後臺帶來額外的工作,從而降低 ClickHouse 的性能。因此,將其設置得足夠大,以便 ClickHouse 可以愉快地接收數據批次,而不會達到內存限制,這一點至關重要。

數據模型

ClickHouse 提供內置的分片和複製,無需任何外部依賴。ClickHouse 的早期版本依賴於 ZooKeeper 來存儲複製信息,但最新版本通過添加 clickhouse-keeper 消除了對 ZooKeeper 的依賴。

爲了跨多個分片讀取數據,我們使用分佈式表,一種特殊的表。這些表本身不存儲任何數據,而是充當存儲實際數據的多個基礎表的代理。

與任何其他數據庫一樣,選擇正確的表 schema 非常重要,因爲它將直接影響性能和存儲利用率。我們想討論將日誌數據存儲到 ClickHouse 中的三種方法。

第一個是最簡單且最嚴格的表模式,您可以在其中指定每個列名稱和數據類型。任何具有此預定義 schema 之外的字段的日誌行都將被刪除。根據我們的經驗,此架構將爲您提供最快的查詢性能。如果您已經知道前面所有可能字段的列表,我們建議使用它。您始終可以通過運行 ALTER TABLE 查詢來添加或刪除列。

第二種模式使用 ClickHouse 的一個非常新的功能,它完成了大部分繁重的工作。您可以將日誌作爲 JSON 對象插入,在幕後,ClickHouse 將瞭解您的日誌架構並動態添加具有適當數據類型和壓縮的新列。僅當您可以很好地控制日誌架構並且總字段數小於 1,000 時,才應使用此架構。一方面,它提供了自動添加新列作爲新日誌字段的靈活性,但與此同時,一個糟糕的應用程序可以輕鬆地破壞 ClickHouse 集羣。

第三種模式將相同數據類型的所有字段存儲在一個數組中,然後使用 ClickHouse 內置數組函數來查詢這些字段。即使字段超過 1,000 個,此架構也能很好地擴展,因爲列數取決於日誌中使用的數據類型。如果某個數組元素被頻繁訪問,可以利用 ClickHouse 的物化列功能將其取出作爲專用列。我們建議採用此模式,因爲它可以防止應用程序記錄過多字段。

數據分區

分區是 ClickHouse 數據的一個單位。 ClickHouse 用戶常犯的一個錯誤是分區鍵過於細化,導致分區過多。由於我們的日誌管道每天都會生成 TB 級的數據,因此我們創建了使用toStartOfHour(dateTime)分區的表。通過這種分區邏輯,當查詢在 WHERE 子句中帶有時間戳時,ClickHouse 就會知道分區並快速檢索它。它還有助於根據數據保留策略設計有效的數據清除規則。

主鍵選擇

ClickHouse 將數據按主鍵排序存儲在磁盤上。因此,選擇主鍵會影響查詢性能並有助於更好的數據壓縮。與關係數據庫不同,ClickHouse 不需要每行都有唯一的主鍵,我們可以插入具有相同主鍵的多行。擁有多個主鍵會對插入性能產生負面影響。ClickHouse 的重要限制之一是,一旦創建表,主鍵就無法更新。

Data skipping indexes

ClickHouse 查詢性能與評估 WHERE 子句時是否可以使用主鍵成正比。我們有很多列,所有這些列都不能成爲主鍵的一部分。因此,對這些列的查詢將必須進行全面掃描,從而導致查詢速度變慢。在傳統數據庫中,可以添加二級索引來處理這種情況。在 ClickHouse 中,我們可以添加另一類索引,稱爲數據跳過索引,它使用布隆過濾器並跳過讀取保證不匹配的重要數據塊。

ABR

我們在 requests_error 日誌上構建了多個儀表板。加載這些儀表板通常會達到 ClickHouse 中爲單個查詢 / 用戶設置的內存限制。

基於這些日誌構建的儀表板主要用於識別異常情況。爲了直觀地識別指標中的異常情況,不需要確切的數字,但可以提供近似的數字。例如,要了解數據中心中錯誤的增加,我們不需要確切的錯誤數量。因此,我們決定使用圍繞 ABR 概念構建的內部庫和工具。

ABR 代表 “自適應比特率” - 術語 ABR 主要用於視頻流服務,其中服務器選擇視頻流的最佳分辨率以匹配客戶端和網絡連接。博客文章《解釋 Cloudflare 的 ABR 分析》對此進行了詳細描述。

換句話說,數據以多種分辨率或採樣間隔存儲,併爲每個查詢選擇最佳解決方案。

ABR 的工作方式是在向 ClickHouse 寫入請求時,它將數據寫入多個具有不同採樣間隔的表中。例如 table_1 存儲 100% 的數據,table_10 存儲 10% 的數據,table_100 存儲 1% 的數據,table_1000 存儲 0.1% 的數據等等。表之間的數據是重複的。 Table_10 將是 table_1 的子集。

Demo

在 Cloudflare 中,我們使用內部庫和工具將數據插入 ClickHouse,但這可以通過使用開源工具 - vector.dev 來實現。如果您想測試 ClickHouse 的日誌攝取是如何工作的,您可以參考或使用 https://github.com/cloudflare/cloudflare-blog/tree/master/2022-08-log-analytics 的演示。

確保您已安裝 docker 並運行docker compose up即可開始。這將打開三個容器,Vector.dev 用於生成矢量演示日誌,將其寫入 ClickHouse,ClickHouse 容器用於存儲日誌,Grafana 實例用於可視化日誌。當容器啓動後,訪問 http://localhost:3000/dashboards 來使用預構建的演示儀表板。

總結

日誌本質上應該是不可變的,而 ClickHouse 最適合處理不可變的數據。我們能夠將關鍵且重要的日誌生成應用程序之一從 Elasticsearch 遷移到更小的 ClickHouse 集羣。

inserter 端的 CPU 和內存消耗減少了八倍。每個使用 600 字節的 Elasticsearch 文檔在 ClickHouse 中減少到每行 60 字節。這種存儲增益使我們能夠在較新的集羣中存儲 100% 的事件。在查詢方面,99 分位的查詢延遲也顯著改善。

Elasticsearch 非常適合全文搜索,ClickHouse 非常適合分析!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/yRn4o08NgSad3McklK1Llg