.NET?core項(xiàng)目AsyncLocal在鏈路追蹤中的應(yīng)用
前言
在項(xiàng)目生產(chǎn)中日志的記錄是必不可少的,在.net項(xiàng)目中,要說日志組件,log4net
絕對(duì)可有一席之地,隨著公司業(yè)務(wù)的發(fā)展,微服務(wù)則必定無可避免。在跨服務(wù)中通過日志進(jìn)行分析性能或者排查故障點(diǎn),如何快速定位日志尤為關(guān)鍵。鏈路追蹤技術(shù)的出現(xiàn)正是解決這些痛點(diǎn)的。
分布式鏈路追蹤需要收集單次請(qǐng)求所經(jīng)過的所有服務(wù),而且為了知道請(qǐng)求細(xì)節(jié),還需要將具體的業(yè)務(wù)日志進(jìn)行串聯(lián),而這一切的基礎(chǔ)就是要通過一個(gè)traceid
從頭傳到尾,相當(dāng)于將該次請(qǐng)求過程產(chǎn)生的所有日志都關(guān)聯(lián)其traceid
,事后排查問題只需要知道traceid
,就可以在日志中拉出與之關(guān)聯(lián)的所有日志。
當(dāng)然不是所有的公司都需要鏈路追蹤,對(duì)于一些小公司,就幾個(gè)單體系統(tǒng),壓根不需要這些。比如我們使用log4net
時(shí),會(huì)在日志模板中加入ThreadId
,例如這樣的模板
"%date [%thread] %-5level - %message%newline"
雖然并發(fā)高時(shí)我們多個(gè)用戶的請(qǐng)求日志都摻雜在一起,但是我們依然可以根據(jù)線程號(hào)將該次請(qǐng)求的日志進(jìn)行串聯(lián)。這在大多時(shí)候都很好的解決了我們的問題。
老傳統(tǒng)做法
即使在體量不大的系統(tǒng)中上面的線程號(hào)很好用了,但是哪有一點(diǎn)不用多線程的業(yè)務(wù)場景呢,當(dāng)一次請(qǐng)求進(jìn)來后可能會(huì)開多個(gè)異步線程去執(zhí)行,那上面的線程號(hào)就顯得力不從心了,就是說沒法一下將相干日志提取出來了。
但是這難不倒我們,我們可以在業(yè)務(wù)開始時(shí)自定義一個(gè)隨便字符串作為該次請(qǐng)求的唯一標(biāo)識(shí),然后將該變量通過參數(shù)傳給下游方法,下游方法也將其一層一層接力傳下去,在打印日志時(shí)都將該字段進(jìn)行輸出,這個(gè)辦法很多人都用過吧。
AspNetCore的TraceIdentifier
難道沒有一種優(yōu)雅的方式能將我們某次請(qǐng)求的過程(包括多線程)進(jìn)行串聯(lián)起來的唯一標(biāo)識(shí)嗎?
在ASPNetCore
中其實(shí)一直有個(gè)不起眼的屬性HttpContext.TraceIdentifier,可以說他就是框架給我們提供的traceid
,我們可以在所需要的地方都注入HttpContext
來獲取該參數(shù),當(dāng)然不許那么麻煩,只需要給日志組件獲取到該值,在任何leave的日志輸出時(shí)日志組件將其輸出即可,這個(gè)完全沒問題,大家可以去深入研究,有些日志組件可以直接配置就可以輸出該TraceIdentifier
值到每一條日志中,也可以將其使用到跨應(yīng)用調(diào)用時(shí)傳遞到下游服務(wù),如http請(qǐng)求可以通過header攜帶該值,下游從header中獲取并作為它自己的TraceIdentifier
繼續(xù)傳遞。
AsyncLocal在鏈路追蹤的應(yīng)用
ThreadLoacl
倒是熟悉,是每個(gè)線程之間隔離的,每個(gè)線程操作的都是自己線程的對(duì)象,能做到各個(gè)線程或不影響。AsyncLocal
并不是一個(gè)新特性,只是用的場景不多,很少被使用
定義
Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.
表示對(duì)于給定異步控制流(如異步方法)是本地?cái)?shù)據(jù)的環(huán)境數(shù)據(jù)。
示例
using System; using System.Threading; using System.Threading.Tasks; class Example { static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>(); static ThreadLocal<string> _threadLocalString = new ThreadLocal<string>(); static async Task AsyncMethodA() { // Start multiple async method calls, with different AsyncLocal values. // We also set ThreadLocal values, to demonstrate how the two mechanisms differ. _asyncLocalString.Value = "Value 1"; _threadLocalString.Value = "Value 1"; var t1 = AsyncMethodB("Value 1"); _asyncLocalString.Value = "Value 2"; _threadLocalString.Value = "Value 2"; var t2 = AsyncMethodB("Value 2"); // Await both calls await t1; await t2; } static async Task AsyncMethodB(string expectedValue) { Console.WriteLine("Entering AsyncMethodB."); Console.WriteLine(" Expected '{0}', AsyncLocal value is '{1}', ThreadLocal value is '{2}'", expectedValue, _asyncLocalString.Value, _threadLocalString.Value); await Task.Delay(100); Console.WriteLine("Exiting AsyncMethodB."); Console.WriteLine(" Expected '{0}', got '{1}', ThreadLocal value is '{2}'", expectedValue, _asyncLocalString.Value, _threadLocalString.Value); } static async Task Main(string[] args) { await AsyncMethodA(); } } // The example displays the following output: // Entering AsyncMethodB. // Expected 'Value 1', AsyncLocal value is 'Value 1', ThreadLocal value is 'Value 1' // Entering AsyncMethodB. // Expected 'Value 2', AsyncLocal value is 'Value 2', ThreadLocal value is 'Value 2' // Exiting AsyncMethodB. // Expected 'Value 2', got 'Value 2', ThreadLocal value is '' // Exiting AsyncMethodB. // Expected 'Value 1', got 'Value 1', ThreadLocal value is ''
簡單理解,就是對(duì)該變量賦值后,之影響自己個(gè)自己的子線程,即當(dāng)前線程發(fā)起的其他線程,包括線程池中的線程,都能獲取到該值,而子線程修改該值,對(duì)父線程來說是無影響的。
而這種特性貌似就是我們尋找那種能夠優(yōu)雅標(biāo)記出同一次請(qǐng)求的特性。定義一個(gè)全局變量,在每次請(qǐng)求的起點(diǎn)對(duì)該變量賦值一個(gè)隨機(jī)字符串,然后本次請(qǐng)求涉及到的所有線程訪問該值,都是我們?cè)谌肟谫x的值。
項(xiàng)目應(yīng)用
我們可以在任意地方定義一個(gè)全局變量,最好是放到LogHelper之中
AspNet4
public static class LogHelper{ public static AsyncLocal<string> Traceid = new AsyncLocal<string>(); ... }
在授權(quán)過濾器中對(duì)該值進(jìn)行賦值,一般授權(quán)過濾最先執(zhí)行,可作為請(qǐng)求的入口點(diǎn)
LogHelper.TraceId.Value?=?Guid.NewGuid().ToString();
在log4net
的LogHelper中使用,日志模板為
"%date [%property{trace}] [%thread] %-5level - %message%newline"
public?static?void?Info(object?message) { ThreadContext.Properties["trace"]?=?TraceId.Value; ????Loger.Info(message); } ...
AspNetCore
注冊(cè)中間件進(jìn)行設(shè)置值,將自己的中間件注冊(cè)靠前點(diǎn)
app.Use(delegate?(HttpContext?ctx,?RequestDelegate?next) { ????LogHelper.TraceId.Value?=?ctx.TraceIdentifier; ????return?next(ctx); });
經(jīng)驗(yàn)證與預(yù)期符合,該實(shí)現(xiàn)方式不依賴AspnetCore框架HttpContext.TraceIdentifier
,提供一種實(shí)現(xiàn)鏈路追蹤中傳遞TraceId
的一種思路,如有不正確之處歡迎指正,如果該思路對(duì)您有幫助,請(qǐng)點(diǎn)贊分享。
以上就是.NET core項(xiàng)目AsyncLocal在鏈路追蹤中的應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于AsyncLocal鏈路追蹤的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
asp.net中C#獲取字符串中漢字的個(gè)數(shù)的具體實(shí)現(xiàn)方法
獲取字符串中漢字原理是判斷漢字編碼然后進(jìn)行判斷是漢字還是數(shù)字了,還有就是利用正則表達(dá)式,同樣是以漢字ascii為標(biāo)準(zhǔn)來獲取2014-02-02ASP.NET MVC實(shí)現(xiàn)多個(gè)按鈕提交的方法
這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC實(shí)現(xiàn)多個(gè)按鈕提交的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Asp.Net實(shí)現(xiàn)FORM認(rèn)證的一些使用技巧(必看篇)
下面小編就為大家?guī)硪黄狝sp.Net實(shí)現(xiàn)FORM認(rèn)證的一些使用技巧(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-08-08Asp.Net 網(wǎng)站優(yōu)化系列之?dāng)?shù)據(jù)庫優(yōu)化分字訣上 分庫
當(dāng)我們的數(shù)據(jù)量很小的時(shí)候,我們會(huì)把用戶表,博客表,論壇表,閃存表等等都砸在一個(gè)庫里,我們的業(yè)務(wù)增長的很好,在不久之后我們盡力的優(yōu)化了查詢,但是效果依然不佳,這時(shí)候用分字訣的時(shí)機(jī)到了。2010-06-06未能加載文件或程序集“XXX”或它的某一個(gè)依賴項(xiàng)。試圖加載格式不正確的程序。
如果你將應(yīng)用程序生成x86而不是Any CPU時(shí),在64位操作系統(tǒng)中不會(huì)出錯(cuò)錯(cuò)誤,而在32位操作系統(tǒng)中可能會(huì)出現(xiàn)以下錯(cuò)誤2012-11-11Asp.net基于ajax和jquery-ui實(shí)現(xiàn)進(jìn)度條
這篇文章主要介紹了Asp.net基于ajax和jquery-ui實(shí)現(xiàn)進(jìn)度條,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12