天天看點

MongoDB應用案例:使用 MongoDB 存儲日志資料

線上運作的服務會産生大量的運作及通路日志,日志裡會包含一些錯誤、警告、及使用者行為等資訊,通常服務會以文本的形式記錄日志資訊,這樣可讀性強,友善于日常定位問題,但當産生大量的日志之後,要想從大量日志裡挖掘出有價值的内容,則需要對資料進行進一步的存儲和分析。

本文以存儲 web 服務的通路日志為例,介紹如何使用 mongodb 來存儲、分析日志資料,讓日志資料發揮最大的價值,本文的内容同樣使用其他的日志存儲型應用。

一個典型的web伺服器的通路日志類似如下,包含通路來源、使用者、通路的資源位址、通路結果、使用者使用的系統及浏覽器類型等

最簡單存儲這些日志的方法是,将每行日志存儲在一個單獨的文檔裡,每行日志在mongodb裡類似

上述模式雖然能解決日志存儲的問題,但使得這些資料分析起來比較麻煩,因為文本分析并不是mongodb所擅長的,更好的辦法時,在把一行日志存儲到mongodb的文檔裡前,先提取出各個字段的值,如下所示,上述的日志被轉換為一個包含很多個字段的文檔。

同時,在這個過程中,如果你覺得有些字段對資料分析沒有任何幫助,則可以直接過濾掉,以減少存儲上的消耗,比如

資料分析不會關心user資訊、request、status資訊,這幾個字段沒必要存儲

objectid裡本身包含了時間資訊,沒必要再單獨存儲一個time字段 (當然帶上time也有好處,time更能代表請求産生的時間,而且查詢語句寫起來更友善,盡量選擇存儲空間占用小的資料類型)

基于上述考慮,一行日志最終存儲的内容可能類似如下

如果要想達到最高的寫入吞吐,可以指定 writeconcern 為 {w: 0}

而如果日志的重要性比較高(比如需要用日志來作為計費憑證),則可以使用更安全的writeconcern級别,比如 {w: 1} 或 {w: "majority"}

同時,為了達到最優的寫入效率,使用者還可以考慮批量的寫入方式,一次網絡請求寫入多條日志。

當日志按上述方式存儲到 mongodb 後,就可以滿足各種查詢需求

如果這種查詢非常頻繁,可以針對path字段建立索引,以高效的服務這類查詢

通過對time字段建立索引,可加速這類查詢

})

通過對host、time建立複合索引可以加速這類查詢

同樣,使用者還可以使用mongodb的aggregation、mapreduce架構來做一些更複雜的查詢分析,在使用時應該盡量建立合理的索引以提升查詢效率。

當寫日志的服務節點越來越多時,日志存儲的服務需要保證可擴充的日志寫入能力以及海量的日志存儲能力,這時就需要使用mongodb sharding來擴充,将日志資料分散存儲到多個shard,關鍵的問題就是shard key的選擇。

一種簡單的方式是使用時間戳來進行分片(如objectid類型的_id,或者time字段),這種分片方式存在如下問題

因為時間戳一直順序增長的特性,新的寫入都會分到同一個shard,并不能擴充日志寫入能力

很多日志查詢是針對最新的資料,而最新的資料通常隻分散在部分shard上,這樣導緻查詢也隻會落到部分shard

按照_id字段來進行hash分片,能将資料以及寫入都均勻都分散到各個shard,寫入能力會随shard數量線性增長,但該方案的問題時,資料分散毫無規律,所有的範圍查詢(資料分析經常需要用到)都需要在所有的shard上進行查找然後合并查詢結果,影響查詢效率。

假設上述場景裡 path 字段的分布是比較均勻的,而且很多查詢都是按path次元去劃分的,那麼可以考慮按照path字段對日志資料進行分片,好處是

寫請求會被均分到各個shard

針對path的查詢請求會集中落到某個(或多個)shard,查詢效率高

不足的地方是

如果某個path通路特别多,會導緻單個chunk特别大,隻能存儲到單個shard,容易出現通路熱點

如果path的取值很少,也會導緻資料不能很好的分布到各個shard

當然上述不足的地方也有辦法改進,方法是給分片key裡引入一個額外的因子,比如原來的shard key是 {path: 1},引入額外的因子後變成

這樣做的效果是分片key的取值分布豐富,并且不會出現單個值特别多的情況。

上述幾種分片方式各有優劣,使用者可以根據實際需求來選擇方案。

分片的方案能提供海量的資料存儲支援,但随着資料越來越多,存儲的成本會不斷的上升,而通常很多日志資料有個特性,日志資料的價值随時間遞減,比如1年前、甚至3個月前的曆史資料完全沒有分析價值,這部分可以不用存儲,以降低存儲成本,而在mongodb裡有很多方法支援這一需求。

ttl 索引目前是背景單線程來定期(預設60s一次)去删除已過期的文檔,如果寫入很多,導緻積累了大量待過期的文檔,則會導緻文檔過期一直跟不上而一直占用着存儲空間。

如果對日志儲存的時間沒有特别嚴格的要求,隻是在總的存儲空間上有限制,則可以考慮使用capped collection來存儲日志資料,指定一個最大的存儲空間或文檔數量,當達到門檻值時,mongodb會自動删除capped collection裡最老的文檔。

比如每到月底就将events集合進行重命名,名字裡帶上目前的月份,然後建立新的events集合用于寫入,比如2016年的日志最終會被存儲在如下12個集合裡

當需要清理曆史資料時,直接将對應的集合删除掉

不足到時候,如果要查詢多個月份的資料,查詢的語句會稍微複雜些,需要從多個集合裡查詢結果來合并

<a href="https://docs.mongodb.com/manual/core/index-ttl/">ttl index</a>

<a href="https://docs.mongodb.com/ecosystem/use-cases/storing-log-data/">storing log data</a>

<a href="https://yq.aliyun.com/articles/54367?spm=5176.8091938.0.0.z9p7fm">mongodb write concern</a>

MongoDB應用案例:使用 MongoDB 存儲日志資料

<a href="/go/1/6?postion=1" target="_blank">雲資料庫 mongodb 版</a>

基于飛天分布式系統和高性能存儲,提供三節點副本集的高可用架構,容災切換,故障遷移完全透明化。并提供專業的資料庫線上擴容、備份復原、性能優化等解決方案。

<a href="/go/1/6?postion=1" target="_blank">了解更多</a>