由於分佈式應用是由多個組件組成的,且這些組件往往是由不同的團隊擁有和操作,所以在與應用程序發生交互時,就會需要跨多個組件執行代碼的分佈式跟蹤。如果用戶遇到了問題,想要確定是哪個組件出現了差錯,基本就是一件不可能完成的事情。
與單體應用程序相比,分佈式應用程序的特點之一就是很難將單個分佈式跟蹤的遙測(如日誌)關聯起來。雖然用戶可以通過查看日誌瞭解到每個組件是如何處理每個請求的,但是很難知道一個組件中的請求和另一個組件中的請求是否屬於同一分佈式跟蹤。
爲了解決這個問題,應用性能管理廠商(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(
ILogger<WeatherForecastProxyController> logger,
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測試、業務上下文傳播等。
原文鏈接: