咱們NetCore下日誌存儲設計


日誌的分類

首先往大的來講,日誌分2種html

①業務日誌: 即業務系統須要查看的日誌, 常見的好比誰何時修改了什麼.git

②參很多天志: 通常是開發人員遇到問題的時候定位用的, 通常不須要再業務系統裏展現.github


對於業務日誌, 咱們如今基本肯定」 業務日誌是業務」 這麼個準則, 即業務日誌應該跟隨着業務表走.數據庫

好比你一個訂單的操做日誌, 那麼訂單表再哪它就應該在哪, 業務日誌應該要跟着你的業務操做同生共死(事務性), 基於上述理念因此業務日誌咱們不會用table存後端


對於參很多天志, 我以爲這個說是後端開發人員的撕逼生命線絕不爲過, 可是同時因爲參很多天志其實並不屬於業務的一部分(徹底沒有這玩意,業務也是能跑的轉,業務系統也不會顯示這些信息)緩存

因此不少時候除開發人員以外的其它利益相關方其實並不在乎是否有這個參很多天志, 甚至很多入門級開發人員也沒法理解其重要性. 併發

並且參很多天志擁有單位價值低, 可是總量卻及其龐大的特色, 也由於這個特色致使數據庫那邊的人(好比DBA)通常也挺抗拒這個的. app

而咱們用Table最主要就是解決參很多天志的問題. 分佈式


日誌存儲體系設計理論

首先一個大原則是咱們但願業務日誌和參很多天志是能串通起來, 好比你進行了這個業務操做而且有了這個業務日誌, 那麼我要能回溯到執行此次業務操做的相關參數. ide

常規的想法是參很多天志裏存一個業務主鍵

可是訂單表的話你存訂單號, 用戶表的話你存用戶Id, 而後再來個別的業務又要存一個別的主鍵, 其實這挺很差擴展的, 而後參很多天志就會變得亂七八糟, 另外你就算存了訂單號你也無法和業務日誌能直接的join出來(通常會匹配下2個日誌的操做時間人肉來看)


而咱們用Application Insights來做爲主力監控, 咱們發現它可以把一個請求/依賴項/異常等信息串聯在一個列表裏  參見: 統一的跨組件事務診斷

image

咱們就很好奇它是怎麼作到的,而後特意扒了一下它的SDK

發如今不一樣年代AppInsights經過不一樣的機制生產了一個在當前請求操做內的Id,而後用於各個操做之間進行關聯,分別是經過:

1.早期的AppInsights裏(Net 4.6以前)是經過CallContext

2.Net 4.6之後是經過AsyncLocal

3.如今NetCore年代則經過Activity

而後它Id分3個,一個是Id自身,一個是ParentId,一個是RootId

這個屬於分佈式追蹤的內容,裏面包含相對較多知識點這裏就不展開太多了,具體能夠看Github上微軟對於Activity的用戶手冊裏有詳細描述 Activity User Guide


AppInsights這個算是給了我較大的啓示,因而乎我就在想,若是個人業務日誌也存下它的那個Id,而後個人參很多天志也存這個Id

那麼我就擁有了一個和業務無關的統一關聯Id(而不是存各個業務表的業務主鍵),同時我甚至能實現相似它的那個「事務診斷」那樣的體驗,我經過一個業務日誌的數據能迅速關聯到個人參很多天志的記錄 


扯了那麼多,具體怎麼作

首先對於如何記錄參很多天志這件事,比較笨的辦法多是以下這樣 

image

厲害點的人可能會把這個步驟放到Filter裏

可是,拜託,都2021年了,咱們來點稍微主流靠譜點的技術吧。


咱們是使用了abp的,我以爲裏面的Audit(審計日誌)特性就蠻不錯,咱們就是經過這個來記錄日誌。

參考文檔 審計日誌 

咱們只須要重寫一下它的 IAuditingStore(裏面只有個SaveAsync方法)

而後在須要的地方打上[Audited]便可

image 


Abp的審計日誌本質是基於Castle的動態代理(DynamicProxy)來實現了AOP,而後它能獲取到一個方法調用的入參/出參/執行時間/異常信息/方法名等各類信息,咱們只要重寫下告訴ABP怎麼存就能夠了

因此記錄日誌的時候只須要打一個特性(並且和Filter不一樣的是我這個特性能夠打在任何基於接口獲取的Public的方法裏,而不侷限於Controller裏)


規範業務日誌表

爲了配套參很多天志,咱們也規範了業務日誌表的存儲。

業務日誌通常會有2種比較常見的存儲模式

①新值舊值得存儲

②徹底拷貝修改前的記錄進行徹底存儲


我採用的是模式②,我我的不太喜歡模式①,首先新老值存儲會帶來存儲量(存儲行數)暴漲的問題,另外新老值存儲我感受很容易遺失一些數據的細節。

我這邊的業務日誌通常是: 原始業務表的數據Copy + 日誌建立時間 + 操做人 + 操做Id + (可選)操做類型(通常是一個枚舉)

至於怎麼Copy原始業務表,AutoMapper映射下不要太簡單

而後結合下上面的理論篇,咱們這裏須要獲取到一個操做Id用於接下來和參很多天志關聯。

在如今Core的年代下直接用Activity便可 

image

在你請求進來的時候它默認就已經構造了,而且能確保當前請求內是惟一,Activity生成的Id也是符合opentelemetry規範的分佈式追蹤Id

其餘一些APM工具(好比我用的AppInsights)如今它內部的追蹤Id也都是基於這套來進行運做 


如何存參很多天志 

首先結合以前說到參很多天志的特色,量大,單位價值低

以前咱們沒更好的存儲介質的時候也是直接存數據庫裏,而後DBA就常常跟咱們說這個太大了,要按期清理下,而後咱們大概是3週一清 

若是一個問題潛伏3周以上對咱們就是個麻煩事了 

並且也所以致使咱們對存儲參很多天志也比較謹慎(稍微量上去了就會叫)


因此咱們認清了以下幾個基本事實:

①關係型數據庫是屬於昂貴存儲,它應該存儲的是價值高的昂貴數據

②數據必須分層,高價值的和低價值的分開 

在結合下前面咱們提到的基於一個分佈式追蹤Id的日誌設計體系,因此還要提供不低於1個索引能力的查詢支持 


後面咱們就用上了Azure Table Storage 

具體Table是什麼我以前有一篇文章有簡單介紹 Azure Table Storage 簡單介紹 


咱們把分佈式追蹤Id做爲PartitionKey,其餘abp裏能提供的數據通通塞Table裏

最後的代碼大概是這樣 

image


裏面摺疊的那個FillAudit方法

image


通過上述設計,咱們整個日誌如今基本就玩的比較轉,一旦有什麼問題,咱們先查詢業務日誌,而後能夠經過任意一條業務日誌在關聯到參很多天志定位到當時是什麼參數進來的,由此提高排查問題的速度 


Note:可能有些眼尖的人會發現個人Async的方法沒await,通過測試證實Table那邊的調用能夠FireAndForgot的,並且基本上也不會丟數據,So這不是Bug或者疏忽,是故意的,反而那個Catch多是一句無效代碼


多說幾句

上面我重寫Audit的是每次來一個請求我就往Table存一條記錄,若是是面向高併發接口(好比查詢類的接口)

上面我所說的這個作法會讓你死得很慘 


正確作法應該是:

將數據先在本地內存緩存一段時間後,當達到某個時間閾值或者數據量累計到必定程度再發送 

這個作法背後仍是蠻複雜的,不過當年咱們再琢磨這個東西的時候發覺咱們用的AppInsights的SDK裏也有這個玩意,咱們直接拿出來稍微定製了下後發覺還真能用。

有興趣能夠看看appInsights相關的代碼 傳送門 

你只要想辦法重寫下它的Send方法,那麼它就能爲你所用了。

相關文章
相關標籤/搜索