日誌資料是最常見的一種海量資料,以擁有大量使用者群體的電商平台為例,雙 11 大促活動期間,它們可能每小時的日誌數量達到百億規模,海量的日誌資料暴增,隨之給技術團隊帶來嚴峻的挑戰。
本文將從海量日誌系統在最佳化、部署、監控方向如何更適應業務的需求入手,重點從多種日誌系統的架構設計對比;後續調優過程:橫向擴充與縱向擴充,分叢集,資料分治,重寫資料鏈路等實際現象與問題展開。
日誌系統架構基準
有過項目開發經驗的朋友都知道:從平台的最初搭建到實現核心業務,都需要有日誌平台為各種業務保駕護航。
如所示,對於一個簡單的日誌應用情境,通常會準備 master/slave 兩個應用。我們只需運行一個 Shell 指令碼,便可查看是否存在錯誤資訊。
隨著業務複雜度的增加,應用情境也會變得複雜。雖然監控系統能夠顯示某台機器或者某個應用的錯誤。
然而在實際的生產環境中,由於實施了隔離,一旦在下側的紅框內某個應用出現了 Bug,則無法訪問到其對應的日誌,也就談不上將日誌取出了。
另外,有些深度依賴日誌平台的應用,也可能在日誌產生的時候就直接採集走,進而刪除掉原始的記錄檔。這些情境給我們日誌系統的維護都帶來了難度。
參考 Logstash,一般會有兩種日誌商務程序:
我們可以從即時性和錯誤分析兩個維度來區分不同的日誌資料情境:
即時,一般適用於我們常說的一級應用,如:直接面向使用者的應用。我們可以自訂各類關鍵字,以方便在出現各種 error 或 exception 時,相關業務人員能夠在第一時間被通知到。
准即時,一般適用於一些專案管理的平台,如:在需要填寫工時的時候出現了宕機,但這並不影響工資的發放。
平台在幾分鐘後完成重啟,我們可以再登入填寫,該情況並不造成原則性的影響。因此,我們可以將其列為準即時的層級。
除了直接採集錯誤與異常,我們還需要進行分析。例如:僅知道某人的體重是沒什麼意義的,但是如果增加了性別和身高兩個指標,那麼我們就可以判斷出此人的體重是否為標準體重。
也就是說:如果能給出多個指標,就可以對龐大的資料進行去噪,然後通過迴歸分析,讓採集到的資料更有意義。
此外,我們還要不斷地去還原數位真實性。特別是對於即時的一級應用,我們要能快速地讓使用者明白他們所碰到現象的真實含義。
例如:商家在上架時錯把商品的價格標籤 100 元標成了 10 元。這會導致商品馬上被搶購一空。
但是這種現象並非是業務的問題,很難被發現,因此我們只能通過日誌資料進行邏輯分析,及時反饋以保證在幾十秒之後將庫存修改為零,從而有效地解決此問題。可見,在此應用情境中,即時分析就顯得非常有用。
最後是追溯,我們需要在擷取曆史資訊的同時,實現跨時間維度的對比與總結,那麼追溯就能夠在各種應用中發揮其關聯性作用了。
上述提及的各個要素都是我們管理日誌的基準。如所示,我們的日誌系統採用的是開源的 ELK 模式:
由於Log Service對於業務來說僅起到了維穩和保障的作用,而且我們需要實現快速、輕量的資料擷取與傳輸,因此不應佔用伺服器太多資源。
在方式上我們採用的是外掛程式模式,包括: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 結合得比較緊密,它們結合在一起需要一點學習成本。
作者:楊津萍
編輯:陳峻、陶家龍、孫淑娟
更多技術乾貨分享
盡在 51CTO技術棧 公眾號