分散式追蹤系統
系統架構從單體轉變成微服務之後,使用者發出的單次請求往往會涉及到多個服務之間的呼叫,以往採用的日誌服務也較難以窺見全貌,當我們需要追蹤某一次請求中間發生了那些事情,我們期望能獲取的資訊不外乎是這次的請求,總共經過哪些服務,每個服務花費了多久的時間,呼叫的順序等等,這就是分散式追蹤系統能夠幫我們做到的事情。只不過每一家系統的 API 都不太一樣,所以 W3C
也有提供一份Trace Context - W3C,讓大家都用同一個標準來實現分散式追蹤。
在這之前看到一堆像是 RequestId
, TraceId
, SpanId
, ParentId
, ActionId
,可能還有很多我沒有列出來的名詞,真的是有看沒有懂,現在至少可以從 W3C 的建議中明確知道一些名詞與他們的用途,其他的應該就是各家廠商自己的定義了,那就有用到再說囉
說明
依據 Trace Context - W3C。這份建議提供了一個標準的定義,讓各家廠商可以遵循,使得各家的追蹤系統可以不在各自為政。所以在version
為00
的情況下,traceparent
的格式就是下面這四個東西的組成
- version
- trace-id
- parent-id/span-id
- trace-flags
所以整個概念就像是下面這張圖一樣
這條呼叫鍊上,則是共用同一個 traceId
,每一個應用程式自己的 scope
就是用相同的 spanId
,在這條呼叫鍊的下一個應用程式則會是另外一個 spanId
,前一個應用程式需要將 traceparent
、tracestate
這兩個 header 資訊正確的傳遞給下一個應用程式。
事前準備
日誌服務:Seq
# create volume folder
mkdir -p D:\docker-volumes\seq
# docker-cli
docker run --name seq -d --restart unless-stopped -e ACCEPT_EULA=Y -v D:\docker-volumes\seq:/data -p 8090:80 -p 5341:5341 datalust/seq
分佈式追踪系統:zipkin
直接使用下列指令可以透過 docker 執行 zipkin,資料則是暫時儲存於記憶體中不保留,若需要保留資料,zipkin
也支援elasticSearch
、mySql
、cassandra
docker run -d -p 9411:9411 openzipkin/zipkin-slim:2
測試專案
測試目標 確認當前 .NET6 Web 網站是否有遵循 W3C 的 traceparent context
測試情境
測試環境由三個應用程式構成
Web(clientApp) -> Proxy(FrontEndApp) -> WebAPI(BackEndApp)
範例程式碼:Github:distributed-tracing-demo
使用者透過瀏覽器開啟網頁,Web
從後端呼叫 Proxy
,而 Proxy
則轉發請求給 WebAPI
查詢取得資料後回應給使用者
測試步驟
開啟 clientApp
之後,透過中斷點檢視 FrontEndApp
跟 BackEndApp
的 request Header
frontEnd Header
backEnd Header
可以注意到 header 裡面的 keyValuePair 有一組是traceparent
, 00-6ef05abe949ce579cc110ab5b289df14-be6d36f4ecc27214-00
traceparent
的格式為version "-" version-format
,而version-format
在version
為00
的定義,又由三個部分組成trace-id "-" parent-id "-" trace-flags
實作分佈式跟踪
實作日誌紀錄
接下來的就是要將這些資訊放到日誌服務 Seq
之內,此處選擇使用 Serilog
套件
# dotnet Core Serilog 套件
dotnet add package Serilog.AspNetCore
# 讓 Log 可以在 Console 顯示
dotnet add package Serilog.Sinks.Console
# 讓 Log 可以送到 Seq
dotnet add package Serilog.Sinks.Seq
# 使其支援 W3C traceparent
dotnet add package Serilog.Enrichers.Span
// program.cs
using Serilog;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
// .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.Enrich.WithSpan()
.WriteTo.Console()
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
try
{
// 原先內容
// ... 略
builder.Host.UseSerilog();
// ... 略
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
確認 Seq 是否有紀錄到正確資訊
開啟 frontEndApp 跟 backendApp,並透過中斷點檢視 backendApp
所接收到的 traceparent
接著到 seq
查看是否有紀錄TraceId
及SpanId
使用 zipkin 檢視 tracing 紀錄
安裝 nuget 套件(照著套件名稱搜尋,並勾選 prerelease
)
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.2.0-rc5" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs" Version="1.0.0-rc9.2" />
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.2.0-rc5" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc9.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc9.2" />
將應用程式的資料直接透過 exporter 傳遞給 zipkin
// program.cs
// .略.
builder.Services.AddControllersWithViews();
builder.Services.AddOpenTelemetryTracing(b => b
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(builder.Configuration.GetValue<string>("Zipkin:ServiceName")))
.AddAspNetCoreInstrumentation()
.AddZipkinExporter());
appsetting.json
裡面的ServiceName
是提供給 zipkin 顯示的服務名稱
//appsetting.json
{
// .略.
"Zipkin": {
"ServiceName": "frontendApp",
"Endpoint": "http://localhost:9411/api/v2/spans"
}
}