.NET Core 3.0 的新改进:针对分布式应用程序的故障诊断和监控
由于分布式應用是由多個組件組成的,且這些組件往往是由不同的團隊擁有和操作,所以在與應用程序發生交互時,就會需要跨多個組件執行代碼的分布式跟蹤。如果用戶遇到了問題,想要確定是哪個組件出現了差錯,基本就是一件不可能完成的事情。
與單體應用程序相比,分布式應用程序的特點之一就是很難將單個分布式跟蹤的遙測 (如日志) 關聯起來。雖然用戶可以通過查看日志了解到每個組件是如何處理每個請求的,但是很難知道一個組件中的請求和另一個組件中的請求是否屬于同一分布式跟蹤。
為了解決這個問題,應用性能管理廠商(APM)會提供從一個組件到另一個組件的分布式跟蹤上下文傳播功能。但是因為很多環境具有異構性、組件歸屬不同的團隊,并通過不同的工具進行監視,因此對分布式應用程序進行一致的測試仍是難點。
隨著 W3C Trace Contex 規范逐步過渡到 Proposed Recommendation maturity 級別,再加上其它供應商和平臺對該規范的支持,上下文傳播的復雜性正在降低。W3C Trace Contex 定義了分布式跟蹤上下文的語義和格式,這使得分布式應用程序中的每個組件都可以理解上下文,并將其傳播到調用的組件中。
為了使得分布式應用程序開發更容易,微軟做了很多努力,例如 Orleans 框架和 Dapr 項目,而在分布式跟蹤上下文傳播,微軟的服務和平臺將采用 W3C Trace Contex 規范。同時,針對分布式跟蹤和日志,.NET Core 3.0 做了很多新工作。
分布式跟蹤和日志記錄
.NET Core 3.0 在分布式跟蹤方面的最新改進:
兩個開箱即用的.NET Core 3.0 應用程序在整個分布式跟蹤中關聯日志;
.NET Core 3.0 如何設置分布式跟蹤上下文,如何自動跨 http 傳播;
同一個分布式跟蹤標識如何被遙測 SDK 使用,例如 OpenTelemetry 和 ASP.NET Core logs。
為了幫助大家更深刻的理解.NET Core 3.0 的新改進,下面我們做了一個示例。
準備工作
該示例中,我們需要用到三個簡單的組件:ClientApp、FrontEndApp 和 BackEndApp。BackEndApp 是一個名為 WeatherApp 的模板 ASP.NET Core 應用程序,可以通過公開的 REST API 來獲取天氣預報。而 FrontEndApp 會通過控制權將所有傳入的請求發送給 BackEndApp。
[ApiController]  | 
[Route("[controller]")]  | 
public class WeatherForecastProxyController : ControllerBase  | 
{  | 
private readonly ILogger<WeatherForecastProxyController> _logger;  | 
private readonly HttpClient _httpClient;  | 
public WeatherForecastProxyController({1}  | 
{1}  | 
ILogger<WeatherForecastProxyController> logger,  | 
{1}  | 
HttpClient httpClient)  | 
{  | 
_logger = logger;  | 
_httpClient = httpClient;  | 
}  | 
[HttpGet]  | 
public async Task<IEnumerable<WeatherForecast>> Get()  | 
{  | 
var jsonStream = await  | 
_httpClient.GetStreamAsync("http://localhost:5001/weatherforecast");  | 
var weatherForecast = await  | 
JsonSerializer.DeserializeAsync<IEnumerable<WeatherForecast>>(jsonStream);  | 
return weatherForecast;  | 
}  | 
}  | 
ClientApp 是一個 .NET Core 3.0 Windows Forms app,會調用 FrontEndApp 進行天氣預報。
private async Task<string> GetWeatherForecast()  | 
{  | 
return await _httpClient.GetStringAsync(  | 
"http://localhost:5000/weatherforecastproxy");  | 
}  | 
需要注意的是,在這段示例中,沒有安裝任何額外的 SDK 和庫。
查看日志
我們通過 ClientApp 調用來觀察 FrontEndApp 和 BackEndApp 生成的日志。
FrontEndApp :
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]  | 
=> ConnectionId:0HLR1BR0PL1CH  | 
=> RequestPath:/weatherforecastproxy  | 
RequestId:0HLR1BR0PL1CH:00000001,  | 
SpanId:|363a800a-4cf070ad93fe3bd8.,  | 
TraceId:363a800a-4cf070ad93fe3bd8,  | 
ParentId:  | 
Executed endpoint 'FrontEndApp.Controllers.WeatherForecastProxyController.Get (FrontEndApp)'  | 
BackEndApp:
info: BackEndApp.Controllers.WeatherForecastController[0]  | 
=> ConnectionId:0HLR1BMQHFKRL  | 
=> RequestPath:/weatherforecast  | 
RequestId:0HLR1BMQHFKRL:00000002,  | 
SpanId:|363a800a-4cf070ad93fe3bd8.94c1cdba_,  | 
TraceId:363a800a-4cf070ad93fe3bd8,  | 
ParentId:|363a800a-4cf070ad93fe3bd8.  | 
Executed endpoint 'FrontEndApp.Controllers.WeatherForecastController.Get (BackEndApp)'  | 
與 magic 一樣,來自兩個獨立應用程序的日志共享相同的 TraceId。ASP.NET Core 3.0 應用程序會初始化分布式跟蹤上下文,并將其傳遞到頭文件中。
FrontEndApp 并沒有收到任何其它的頭文件,出現這種情況的原因是在 ASP.NET Core 應用程序中,分布式跟蹤是由 ASP.NET Core 框架自身在每次傳入請求時啟動的。
啟動分布式跟蹤
相信很多人都注意到了 Windows 窗體應用 ClientApp 和 ASP.NET Core FrontEndApp 在行為上的差異。ClientApp 未設置任何分布式跟蹤上下文,因此 FrontEndApp 也不會收到。設置分布式操作最簡單的方法是,使用 DiagnosticSource 包中名為 Activity 的 API。
private async Task<string> GetWeatherForecast()  | 
{  | 
var activity = new Activity("CallToBackend").Start();  | 
try  | 
{  | 
return await _httpClient.GetStringAsync(  | 
"http://localhost:5000/weatherforecastproxy");  | 
}  | 
finally  | 
{  | 
activity.Stop();  | 
}  | 
}  | 
啟動之后,HttpClient 就知道需要傳播分布式跟蹤上下文。需要注意的是,ClientApp、FrontEndApp 和 BackEndApp 都共享同一個 TraceId。
W3C Trace Context
上下文使用 Request-Id 進行傳播,這是從 ASP 中引入的,是默認生效的。但是,W3C Trace Context 正被廣泛采用,因此,我們建議切換到 W3C Trace Context 的上下文傳播格式。
在.NET Core 3.0 中,切換到 W3C Trace Context 格式只需要在主方法中添加一行代碼:
static void Main()  | 
{  | 
Activity.DefaultIdFormat = ActivityIdFormat.W3C;  | 
…  | 
Application.Run(new MainForm());  | 
}  | 
當 FrontEndApp 收到來自 ClientApp 的請求時,你會在請求中看到一個 traceparent 頭文件:
通過這個頭文件,.NET Core 應用程序就會明白現在需要通過 W3C Trace Context 格式來進行傳播調用。需要注意的是,.NET Core 應用程序可以自動識別 W3C Trace Context 的正確格式,但是將分布式跟蹤上下文的默認格式切換到 W3C Trace Context,可以在異構環境中實現更好的互操作性。
所有屬性為 TraceId 和 SpanId 的日志:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]  | 
=> ConnectionId:0HLQV2BC3VP2T  | 
=> RequestPath:/weatherforecast  | 
RequestId:0HLQV2BC3VP2T:00000001,  | 
SpanId:da13aa3c6fd9c146,  | 
TraceId:f11a03e3f078414fa7c0a0ce568c8b5c,  | 
ParentId:5076c17d0a604244  | 
Request starting HTTP/1.1 GET http://localhost:5000/weatherforecast  | 
OpenTelemetry 的 Activity?和分布式跟蹤
OpenTelemetry 提供了一組 API、庫、代理和控制器服務,用于從應用程序捕獲分布式跟蹤和度量。
在調用時啟動 AddOpenTelemetry,就可以在 BackEndApp 上啟用 OpenTelemetry:
services.AddOpenTelemetry(b =>  | 
b.UseZipkin(o => {  | 
o.ServiceName="BackEndApp";  | 
o.Endpoint=new Uri("http://zipkin /api/v2/spans");  | 
})  | 
.AddRequestCollector());  | 
FrontEndApp 日志中的 TraceId 與 BackEndApp 中的 TraceId 匹配。
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]  | 
=> ConnectionId:0HLR2RC6BIIVO  | 
=> RequestPath:/weatherforecastproxy  | 
RequestId:0HLR2RC6BIIVO:00000001,  | 
SpanId:54e2de7b9428e940,  | 
TraceId:e1a9b61ec50c954d852f645262c7b31a,  | 
ParentId:69dce1f155911a45  | 
=> FrontEndApp.Controllers.WeatherForecastProxyController.Get (FrontEndApp)  | 
Executed action FrontEndApp.Controllers.WeatherForecastProxyController.Get (FrontEndApp) in 3187.3112ms  | 
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]  | 
=> ConnectionId:0HLR2RLEHSKBV  | 
=> RequestPath:/weatherforecast  | 
RequestId:0HLR2RLEHSKBV:00000001,  | 
SpanId:0e783a0867544240,  | 
TraceId:e1a9b61ec50c954d852f645262c7b31a,  | 
ParentId:54e2de7b9428e940  | 
=> BackEndApp.Controllers.WeatherForecastController.Get (BackEndApp)  | 
Executed action BackEndApp.Controllers.WeatherForecastController.Get (BackEndApp) in 3085.9111ms  | 
此外,Zipkin 將報告相同的跟蹤,因此,可以將分布式跟蹤工具收集的分布式跟蹤與來自計算機的日志關聯起來。當 ClientApp 遇到問題時,可以將此 TraceId 提供給用戶,由于用戶和應用程序可以共享,因此能夠更加容易的跨組件發現相應的日志和分布式跟蹤。
另外,用戶可以輕松啟用對三個組件的監控,并在甘特圖上查看:
ASP .NET Core 應用程序與分布式跟蹤集成
APM 廠商收集到的遙測數據和 ASP .NET Core 使用的分布式跟蹤上下文是相關的。因此,ASP .NET Core 3.0 應用程序非常適合不同團隊擁有不同組件的場景。
例如,下圖中的兩個應用 A 和 C,啟用了類似于 OpenTelemetry 的 SDK 遙測采集。如果不使用 ASP .NET Core 3.0,那么應用程序 B 就會“破壞”跟蹤,導致分布式跟蹤無法起作用。
在大多數部署中,ASP.NET Core 應用程序配置為啟用基本日志記錄,因此應用程序 B 將傳播分布式跟蹤上下文。而來自 A 和 C 的分布軌跡將相互關聯。在以前的應用程序中,如果 ClientApp 和 BackEndApp 被感知,而 FrontEndApp 沒有被感知,仍然可以看到分布式跟蹤是相關的:
ASP.NET Core 應用程序非常適合服務網格環境。在服務網格部署中,上圖中的 A 和 C 可以表示服務網格。為了讓服務網格請求進入和離開組件 B,應用程序必須包含某些頭文件。
雖然 Istio 能夠自動發送 span,但是仍需要一些提示來連接整個跟蹤。應用程序需要使用合適的 HTTP 頭文件,以便在發送 span 消息時,能夠正確關聯到單個跟蹤中。如果是采用 W3C Trace Context 格式,ASP.NET Core 應用程序則無需做任何改變。
傳遞附加上下文
如果你希望能夠在分布式應用程序的組件之間共享更多上下文,可以添加以下屬性:
private async Task<string> GetWeatherForecast()  | 
{  | 
var activity = new Activity("CallToBackend")  | 
.AddBaggage("appVersion", "v1.0")  | 
.Start();  | 
try  | 
{  | 
return await _httpClient.GetStringAsync(  | 
"http://localhost:5000/weatherforecastproxy");  | 
}  | 
finally  | 
{  | 
activity.Stop();  | 
}  | 
}  | 
服務器端,在 FrontEndApp 和 BackEndApp 可以看到一個額外的頭文件 Correlation-Context。
使用 Activity.Baggage:
var appVersion = Activity.Current.Baggage.FirstOrDefault(b => b.Key == "appVersion").Value;  | 
using (_logger.BeginScope($"appVersion={appVersion}"))  | 
{  | 
_logger.LogInformation("this weather forecast is from random source");  | 
}  | 
作用域中包含 appVersion:
info: FrontEndApp.Controllers.WeatherForecastController[0]  | 
=> ConnectionId:0HLQV353507UG  | 
=> RequestPath:/weatherforecast  | 
RequestId:0HLQV353507UG:00000001,  | 
SpanId:37a0f7ebf3ecac42,  | 
TraceId:c7e07b7719a7a3489617663753f985e4,  | 
ParentId:f5df77ba38504846  | 
=> FrontEndApp.Controllers.WeatherForecastController.Get (BackEndApp)  | 
=> appVersion=v1.0  | 
this weather forecast is from random source  | 
未來發展
隨著 ASP.NET Core 3.0 的改進,很多 ASP .NET Core 包含的功能可能就難以使用了,比如開發人員和 DevOps 想要做一個交鑰匙遙測解決方案,需要和很多 APM 的廠商來做。但是,我們在 OpenTelemetry 方面會加大投入,使得更多 ASP .NET Core 用戶能夠在監控和故障排除方面變得更容易。
我們會幫助用戶采用 W3C Trace Context,并且在 ASP .NET Core 的未來版本中可能將其作為默認的分布式跟蹤上下文傳播格式。
另外,我們還會專注于改進分布式上下文傳播場景。與 Monolits 相比,分布式應用程序在單個分布式跟蹤的生存期缺少公共共享狀態,而這種共享狀態可以用于基本的日志記錄、用于請求的高級路由、實驗、A/B 測試、業務上下文傳播等。
原文鏈接:
https://devblogs.microsoft.com/aspnet/improvements-in-net-core-3-0-for-troubleshooting-and-monitoring-distributed-apps/
總結
以上是生活随笔為你收集整理的.NET Core 3.0 的新改进:针对分布式应用程序的故障诊断和监控的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 【译】gRPC vs HTTP APIs
 - 下一篇: 【翻译】.NET Core3.1发布