老生常談:百億級日誌系統架構的設計及優化

本文來自於51CTO技術棧,日誌數據是最常見的一種海量數據,以擁有大量用戶群體的電商平臺為例,雙 11 大促活動期間,它們可能每小時的日誌數量達到百億規模,海量的日誌數據暴增,隨之給技術團隊帶來嚴峻的挑戰。

老生常談:百億級日誌系統架構的設計及優化

本文將從海量日誌系統在優化、部署、監控方向如何更適應業務的需求入手,重點從多種日誌系統的架構設計對比;後續調優過程:橫向擴展與縱向擴展,分集群,數據分治,重寫數據鏈路等實際現象與問題展開。

日誌系統架構基準

有過項目開發經驗的朋友都知道:從平臺的最初搭建到實現核心業務,都需要有日誌平臺為各種業務保駕護航。

老生常談:百億級日誌系統架構的設計及優化

如上圖所示,對於一個簡單的日誌應用場景,通常會準備 master/slave 兩個應用。我們只需運行一個 Shell 腳本,便可查看是否存在錯誤信息。

隨著業務複雜度的增加,應用場景也會變得複雜。雖然監控系統能夠顯示某臺機器或者某個應用的錯誤。

然而在實際的生產環境中,由於實施了隔離,一旦在上圖下側的紅框內某個應用出現了 Bug,則無法訪問到其對應的日誌,也就談不上將日誌取出了。

另外,有些深度依賴日誌平臺的應用,也可能在日誌產生的時候就直接採集走,進而刪除掉原始的日誌文件。這些場景給我們日誌系統的維護都帶來了難度。

老生常談:百億級日誌系統架構的設計及優化

參考 Logstash,一般會有兩種日誌業務流程:

正常情況下的簡單流程為:應用產生日誌→根據預定義的日誌文件大小或時間間隔,通過執行 Logrotation,不斷刷新出新的文件→定期查看→定期刪除。

複雜應用場景的流程為:應用產生日誌→採集→傳輸→按需過濾與轉換→存儲→分析與查看。

老生常談:百億級日誌系統架構的設計及優化

我們可以從實時性和錯誤分析兩個維度來區分不同的日誌數據場景:

實時,一般適用於我們常說的一級應用,如:直接面向用戶的應用。我們可以自定義各類關鍵字,以方便在出現各種 error 或 exception 時,相關業務人員能夠在第一時間被通知到。

準實時,一般適用於一些項目管理的平臺,如:在需要填寫工時的時候出現了宕機,但這並不影響工資的發放。

平臺在幾分鐘後完成重啟,我們可以再登錄填寫,該情況並不造成原則性的影響。因此,我們可以將其列為準實時的級別。

除了直接採集錯誤與異常,我們還需要進行分析。例如:僅知道某人的體重是沒什麼意義的,但是如果增加了性別和身高兩個指標,那麼我們就可以判斷出此人的體重是否為標準體重。

也就是說:如果能給出多個指標,就可以對龐大的數據進行去噪,然後通過迴歸分析,讓採集到的數據更有意義。

此外,我們還要不斷地去還原數字的真實性。特別是對於實時的一級應用,我們要能快速地讓用戶明白他們所碰到現象的真實含義。

例如:商家在上架時錯把商品的價格標籤 100 元標成了 10 元。這會導致商品馬上被搶購一空。

但是這種現象並非是業務的問題,很難被發現,因此我們只能通過日誌數據進行邏輯分析,及時反饋以保證在幾十秒之後將庫存修改為零,從而有效地解決此問題。可見,在此應用場景中,實時分析就顯得非常有用。

最後是追溯,我們需要在獲取歷史信息的同時,實現跨時間維度的對比與總結,那麼追溯就能夠在各種應用中發揮其關聯性作用了。

老生常談:百億級日誌系統架構的設計及優化

上述提及的各個要素都是我們管理日誌的基準。如上圖所示,我們的日誌系統採用的是開源的 ELK 模式:

ElasticSearch(後簡稱 ES),負責後端集中存儲與查詢工作。

單獨的 Beats 負責日誌的蒐集。FileBeat 則改進了 Logstash 的資源佔用問題;TopBeat 負責蒐集監控資源,類似系統命令 top 去獲取 CPU 的性能。

由於日誌服務對於業務來說僅起到了維穩和保障的作用,而且我們需要實現快速、輕量的數據採集與傳輸,因此不應占用服務器太多資源。

在方式上我們採用的是插件模式,包括:input 插件、output 插件、以及中間負責傳輸過濾的插件。這些插件有著不同的規則和自己的格式,支持著各種安全性的傳輸。

日誌系統優化思路

老生常談:百億級日誌系統架構的設計及優化

有了上述日誌的架構,我們針對各種實際的應用場景,進一步提出了四個方面的優化思路:

基礎優化

內存:如何分配內存、垃圾回收、增加緩存和鎖。

網絡:網絡傳輸序列化、增加壓縮、策略、散列、不同協議與格式。

CPU:用多線程提高利用率和負載。

此處利用率和負載是兩個不同的概念:

利用率:在用滿一個核後再用下一個內核,利用率是逐步升高的。

負載:一下子把八個核全用上了,則負載雖然是滿的,但是利用率很低。即,每核都被佔用了,但是所佔用的資源卻不多,計算率比較低下。

磁盤:嘗試通過文件合併,減少碎片文件的產生,並減少尋道次數。同時在系統級別,通過修改設置,關閉各種無用的服務。

平臺擴展

做加減法,或稱替代方案:無論是互聯網應用,還是日常應用,我們在查詢時都增加了分佈式緩存,以有效提升查詢的效率。另外,我們將不被平臺使用到的地方直接關閉或去除。

縱向擴展:如增加擴展磁盤和內存。

橫向擴展:加減/平行擴展,使用分佈式集群。

數據分治

根據數據的不同維度,對數據進行分類、分級。例如:我們從日誌中區分error、info、和 debug,甚至將 info 和 debug 級別的日誌直接過濾掉。

數據熱點:例如:某種日誌數據在白天的某個時間段內呈現暴漲趨勢,而晚上只是平穩產生。我們就可以根據此熱點情況將它們取出來單獨處理,以打散熱點。

系統降級

我們在對整體業務進行有效區分的基礎上,通過制定一些降級方案,將部分不重要的功能停掉,以滿足核心業務。

日誌系統優化實踐

老生常談:百億級日誌系統架構的設計及優化

面對持續增長的數據量,我們雖然增加了許多資源,但是並不能從根本上解決問題。

特別體現在如下三方面:

日誌產生量龐大,每天有幾百億條。

由於生產環境隔離,我們無法直接查看到數據。

代理資源限制,我們的各種日誌採集和系統資源採集操作,不可超過業務資源的一個核。

一級業務架構

老生常談:百億級日誌系統架構的設計及優化

我們日誌系統的層次相對比較清晰,可簡單分為數據接入、數據存儲和數據可視化三大塊。

具體包括:

Rsyslog,是目前我們所接觸到的採集工具中最節省性能的一種。

Kafka,具有持久化的作用。當然它在使用到達一定數據量級時,會出現 Bug。

Fluentd,它與 Rsyslog 類似,也是一種日誌的傳輸工具,但是它更偏向傳輸服務。

ES 和 Kibana。

老生常談:百億級日誌系統架構的設計及優化

該架構在實現上會用到 Golang、Ruby、Java、JS 等不同的語言。在後期改造時,我們會將符合 Key-Value 模式的數據快速地導入 HBase 之中。

基於 HBase 的自身特點,我們實現了它在內存層的 B+ 樹,並且持久化到我們的磁盤之上,從而達到了理想的快速插入的速度。這也正是我們願意選擇 HBase 作為日誌方案的原因。

二級業務架構

老生常談:百億級日誌系統架構的設計及優化

我們直接來看二級業務架構的功能圖,它是由如下流程串聯而成的:

在完成了數據採集之後,為了節省自己佔用磁盤的空間,許多應用會完全依賴於我們的日誌系統。因此在數據採集完以後,我們增加了一個持久緩存。

完成緩存之後系統執行傳輸。傳輸的過程包括:過濾和轉換,這個過程可以進行數據抽稀。值得強調的是:如果業務方儘早合作並給予我們一些約定的話,我們就能夠通過格式化來實現結構化的數據。

隨後執行的是分流,其主要包括兩大塊:一種是 A 來源的數據走 A 通道,B 來源的數據走 B 通道。另一種是讓 A 數據流入到我們的存儲設備,並觸發保護機制。即為了保障存儲系統,我們額外增加了一個隊列。

例如:隊列為 100,裡面的一個 chunk 為 256 兆,我們現在設置高水位為 0.7、低水位為 0.3。

在寫操作的堆積時,由於我們設置了 0.7,即 100 兆赫。那麼在一個 256 兆會堆積到 70 個 chunk 時,我們往該存儲平臺的寫速度就已經跟不上了。

此時高水位點會被觸發,不允許繼續寫入,直到整個寫入過程把該 chunk 消化掉,並降至 30 個時,方可繼續往裡寫入。我們就是用該保護機制來保護後臺以及存儲設備的。

接著是存儲,由於整個數據流的量會比較大,因此在存儲環節主要執行的是存儲的索引、壓縮、和查詢。

最後是 UI 的一些分析算法,運用 SQL 的一些查詢語句進行簡單、快速地查詢。

老生常談:百億級日誌系統架構的設計及優化

通常從採集(logstash/rsyslog/heka/filebeat)到面向緩存的 Kafka 是一種典型的寬依賴。

所謂寬依賴,是指每個 App 都可能跟每個 Broker 相關聯。在 Kafka 處,每次傳輸都要在哈希之後,再把數據寫到每個 Broker 上。

而窄依賴,則是其每一個 Fluentd 進程都只對應一個 Broker 的過程。最終通過寬依賴過程寫入到 ES。

採集

如 Rsyslog 不但佔用資源最少,而且可以添加各種規則,它還能支持像 TSL、SSL 之類的安全協議。

Filebeat 輕量,在版本 5.x 中,Elasticsearch 具有解析的能力(像 Logstash 過濾器)— Ingest。

這也就意味著可以將數據直接用 Filebeat 推送到 Elasticsearch,並讓 Elasticsearch 既做解析的事情,又做存儲的事情。

Kafka

老生常談:百億級日誌系統架構的設計及優化

接著是 Kafka,Kafka 主要實現的是順序存儲,它通過 topic 和消息隊列的機制,實現了快速地數據存儲。

而它的缺點:由於所有的數據都向 Kafka 寫入,會導致 topic 過多,引發磁盤競爭,進而嚴重拖累 Kafka 的性能。

另外,如果所有的數據都使用統一標籤的話,由於不知道所採集到的數據具體類別,我們將很難實現對數據的分治。

因此,在後面的優化傳輸機制方面,我們改造並自己實現了順序存儲的過程,進而解決了一定要做持久化這一安全保障的需求。

Fluentd

老生常談:百億級日誌系統架構的設計及優化

Fluentd 有點類似於 Logstash,它的文檔和插件非常齊全。其多種插件可保證直接對接到 Hadoop 或 ES。

就接入而言,我們可以採用 Fluentd 到 Fluentd 的方式。即在原有一層數據接入的基礎上,再接一次 Fluentd。同時它也支持安全傳輸。當然我們在後面也對它進行了重點優化。

ES+Kibana

老生常談:百億級日誌系統架構的設計及優化

最後我們用到了 ES 和 Kibana。ES 的優勢在於通過 Lucene 實現了快速的倒排索引。

由於大量的日誌是非結構化的,因此我們使用 ES 的 Lucene 進行包裝,以滿足普通用戶執行非結構化日誌的搜索。而 Kibana 則基於 Lucene 提供可視化顯示工具。

問題定位與解決

下面介紹一下我們碰到過的問題和現象,如下這些都是我們著手優化的出發點:

傳輸服務器的 CPU 利用率低下,每個核的負載不飽滿。

傳輸服務器 Full gc 的頻次過高。由於我們是使用 Ruby 來實現的過程,其內存默認設置的數據量有時會過大。

存儲服務器出現單波峰現象,即存儲服務器磁盤有時會突然出現性能直線驟升或驟降。

頻繁觸發高水位。如前所述的高水位保護機制,一旦存儲磁盤觸發了高水位,則不再提供服務,只能等待人工進行磁盤“清洗”。

如果 ES 的一臺機器“掛”了,則集群就 hang 住了。即當發現某臺機器無法通訊時,集群會認為它“掛”了,則快速啟動數據恢復。而如果正值系統繁忙之時,則此類數據恢復的操作會更加拖累系統的整體性能。

老生常談:百億級日誌系統架構的設計及優化

由於所有數據都被寫入 Kafka,而我們只用到了一個 topic,這就造成了每一類數據都要經過不一定與之相關的規則鏈,並進行不一定適用的規則判斷,因此數據的傳輸效率整體被降低了。

Fluentd 的 host 輪詢機制造成高水位頻發。由於 Fluentd 在與 ES 對接時遵循一個默認策略:首選前五臺進行數據寫入,即與前五臺的前五個接口交互。

在我們的生產環境中,Fluentd 是用 CRuby 寫的。每一個進程屬於一個 Fluentd 進程,且每一個進程都會對應一個 host 文件。

而該 host 文件的前五個默認值即為 ES 的寫入入口,因此所有機器都會去找這五個入口。

倘若有一臺機器宕機,則會輪詢到下一臺。如此直接造成了高水位的頻繁出現、和寫入速度的下降。

眾所周知,對日誌的查詢是一種低頻次的查詢,即只有在出現問題時才會去查看。但是在實際操作中,我們往往通過檢索的方式全部取出,因此意義不大。

另外 ES 為了達到較好的性能,會將數據存儲在 raid0 中,存儲的時間跨度往往會超過 7 天,因此其成本也比較高。

通過對數據的實時線分析,我們發現並未達到寫入/寫出的平衡狀態。

為了提高 Fluentd 的利用率,我們用 Kafka 去數據的時候提高了量,原來是 5 兆,現在我們改到了 6 兆。

如果只是單純傳輸,不論計算的話,其實可以改更高。只不過因為我們考慮到這裡包含了計算的一些東西,所以只提到了 6 兆。

我們的 Fluentd 是基於 JRuby 的,因為 JRuby 可以多線程,但是我們的 CRuby 沒有任何意義。

為了提高內存,我把 Ruby 所有的內存機制瞭解了一下,就是散列的一些 host 文件,因為我們每個進程都選前五列就可以了,我多開了幾個口。ES 的優化這一塊,在上 ES 之前,我們已經有人做過一次優化了。

因為基於我剛才說的有時候日誌量很高,有時候日誌量很少。我們會考慮做動態配置。

因為 ES 就是支持動態配置的,所以它動態配置的時候,我們在某些場景下可以提高它的寫入速度,某些場景下可以支持它的這種查詢效率。我們可以嘗試去做一些動態配置負載。

改造一:存儲降低

老生常談:百億級日誌系統架構的設計及優化
老生常談:百億級日誌系統架構的設計及優化

降低存儲在整體架構上並沒有太大變化,我們只是在傳輸到 Fluentd 時把天數降下來,改成了一天。

同時,我們直接進行了分流,把數據往 Hadoop 裡寫,而把一些符合 Kibana 的數據直接放入 ES。

上面提過,日誌查詢是低頻次的,一般需要查詢兩天以上數據的可能性很小,因此我們降低存儲是非常有意義的。

改造二:數據分治

老生常談:百億級日誌系統架構的設計及優化

我們在日誌文件節點數較少(機器數量小於 5 臺)的情況下,去掉了 Kafka 層。由於 Fluentd 可以支持數據和大文件存儲,因此數據能夠被持久化地存入磁盤。

我們給每個應用都直接對應了一個 tag,以方便各個應用對應到自己的 tag、遵循自己的固定規則、並最終寫入 ES,這樣就方便了出現問題的各自定位。

另外,我們運用延遲計算和文件切分也能快速地找到問題的根源。因此我們節約了 Kafka 和 ES 各種計算資源。

在實際操作中,由於 HBase 不用去做 raid,它自己完全能夠控制磁盤的寫入,因此我們進行了數據壓縮。就其效果而言,ES 的存儲開銷大幅降低。

在後期,我們也嘗試過一種更為極端的方案:讓用戶直接通過客戶端的 Shell 去查詢數據,並採用本地緩存的留存機制。

優化效果

老生常談:百億級日誌系統架構的設計及優化

優化的效果如下:

服務器資源的有效利用。在實施了新的方案之後,我們省了很多服務器,而且單臺服務器的存儲資源也節省了 15%。

單核處理每秒原來能夠傳輸 3000 條,實施後提升到了 1.5~1.8 萬條。而且,在服務器單獨空跑,即不加任何計算時,單核每秒能傳輸近 3 萬條。

很少觸發 ES 保護機制。原因就是我們已把數據分流出來了。

以前歷史數據只能存 7 天,由於我們節省了服務器,因此我們現在可以存儲更長時間的數據。而且,對於一些他人查詢過的日誌,我們也會根據最初的策略,有選擇性地保留下來,以便追溯。

日誌系統優化總結

老生常談:百億級日誌系統架構的設計及優化
老生常談:百億級日誌系統架構的設計及優化
老生常談:百億級日誌系統架構的設計及優化

關於日誌平臺優化,我總結了如下幾點:

由於日誌是低頻次的,我們把歷史數據存入了廉價存儲之中,普通用戶需要的時候,我們再導到 ES 裡,通過 Kibana 的前端界面便可快速查詢到。而對於程序員來說,則不需要到 ES 便可直接查詢到。

數據存在的時間越長,則意義越小。我們根據實際情況制定了有效的、留存有意義數據的策略。

順序寫盤替代內存。例如:區別於平常的隨機寫盤,我們在操作讀寫一個流文件時採取的是按順序寫數據的模式。

而在存儲量大的時候,則應當考慮 SSD。特別是在 ES 遇到限流時,使用 SSD 可以提升 ES 的性能。

提前定製規範,從而能夠有效解決後期分析等工作。

日誌格式

老生常談:百億級日誌系統架構的設計及優化

如上圖所示,常用的日誌格式類型包括:uuid、timestamp、host 等。

特別是 host,由於日誌會涉及到幾百個節點,有了 host 類型,我們就能判定是哪臺機器上的標準。而圖中其他的環境變量類型,則能夠有效地追溯到一些歷史的信息。

日誌方案

老生常談:百億級日誌系統架構的設計及優化

如上圖所示,我們通過 Rsyslog 可以直接將採集端的數據寫入文件或數據庫之中。

當然,對於一些暫時用不上的日誌,我們不一定非要實施過濾傳輸的規則。

老生常談:百億級日誌系統架構的設計及優化

如上圖,Fluentd 也有一些傳輸的規則,包括:Fluentd 可以直接對接 Fluentd,也可以直接對接 MongoDB、MySQL 等。

另外,我們也有一些組件可以快速地對接插件和系統,例如讓 Fluentd 和 Rsyslog 能夠直接連到 ES 上。

這是我個人給大家定製的一些最基本的基線,我認為日誌從採集、緩存、傳輸、存儲,到最終可視化,分成了三套基線。

採集到存儲是最簡單的一個,像 Rsyslog 到 hdfs 或者其他 filesystem,我們有這種情況。

比較常見的情況,就是從採集、傳輸、到存儲可視化,然後形成最終我們現在最複雜的一套系統,大家可以根據實際情況取捨。

最後是我考慮到一個實際情況,假如這個案例,我們儘可能少的佔有服務器,然後傳輸需要過濾轉換,日誌可以比較簡單,符合這種 Key value(KV)格式。

我們可以按照取了一個 Rsyslog、取了一個 Fluentd、取了一個 Hbase,取了一個 echars 等這麼一個方式做一個方案就可以了。

我覺得 Rsyslog、Fluentd、heka 這些都可以做採集。然後傳輸這塊有 Fluentd 傳輸,因為 Fluentd 和 Kafka 到插件非常靈活可以直接對接我們很多存儲設備,也可以對應很多的文件、連 ES 都可以。

可視化可以用 Kibana,主要是跟 ES 結合得比較緊密,它們結合在一起需要一點學習成本。


分享到:


相關文章: