Asp.Net Core&Jaeger實現鏈路追蹤
前言
隨著應用愈發複雜,請求的鏈路也愈發複雜,微服務化下,更是使得不同的服務分布在不同的機器,地域,語言也不盡相同。因此需要藉助工具幫助分析,跟蹤,定位請求中出現的若干問題,以此來保障服務治理,鏈路追蹤也就出現了。
OpenTracing協議
OpenTracing是一套分散式追蹤協議,與平台,語言、廠商無關的Trace協議,統一介面,使得開發人員能夠方便的添加或更換更換不同的分散式追蹤系統。
同樣作為分散式追蹤協議的還有OpenCensus,以及兩者的合併體OpenTelemetry。
Jaeger介紹
Jaeger[ˈdʒɛgər]是Uber推出的一款開源分散式追蹤系統,兼容OpenTracing API,已在Uber大規模使用,且已加入CNCF開源組織(Cloud Native Computing Foundation-雲原生計算基金會)。其主要功能是聚合來自各個異構系統的實時監控數據。
Jager提供了一套完整的追蹤系統包括Jaeger-client、Jaeger-agent、Jaeger-collector、Database和Jaeger-query UI等基本組件。
- Jaeger-client:為不同開發語言實現了符合OpenTracing協議的客戶端。
- Jaeger-agent:一個監聽在UDP埠上接收鏈路數據的網路守護進程,它從應用程式收集,批處理,並發送給Collector,(也可以沒有這個,client直接上報)。
- Jaeger-collector:負責接收Jaeger-client或Jaeger-agent上報的調用鏈路數據,並通過處理管道運行它們,該管道驗證跟蹤、對它們進行索引、執行任何轉換並最終保存到記憶體或外部存儲系統中,供UI展示。
- Jaeger-query:查詢服務從存儲中檢索跟蹤並呈現 UI 來顯示它們。
Jaeger安裝
在個人使用或者測試上,Jaeger提供了jaegertracing/all-in-one鏡像,搭建過程十分簡單,數據存儲在記憶體中,但需要注意容器掛了後數據就沒了。
docker run -d -p 6831:6831/udp -p 16686:16686 jaegertracing/all-in-one:latest
創建容器運行後,可以訪問ip:16686查看Jaeger的儀錶面板
Jaeger應用
服務設計
簡化大部分服務設計,整個結構上差不多是如下所示,服務層常見金字塔結構,服務上下游明確,以避免服務間的循環依賴。
此處建立四個服務以及一個BFF網關層,以滿足服務同步調用,服務間上下游調用,以及服務間事件通訊。
- JaegerDemo.BFF.Host
- JaegerDemo.AService.Host
- JaegerDemo.BService.Host
- JaegerDemo.CService.Host
- JaegerDemo.DService.Host
為這幾個服務設定期望如下
- 執行Get請求時,從Gateway調用,請求A服務,在同步請求B和C服務,拿到結果組裝後對外返回。
- 執行Post請求時,從Gateway調用,請求A服務,在發布事件到MQ中,D服務訂閱事件,數據寫入到Sqlite中。
Nuget包引用
- Jaeger,用來上傳數據到Jaeger。
- OpenTracing.Contrib.NetCore,基於OpenTracing.Net的增強,用來採集應用數據。
- MassTransit和MassTransit.RabbitMQ,用來完成事件的發布訂閱。
<ItemGroup>
<PackageReference Include="OpenTracing" Version="0.12.1" />
<PackageReference Include="Jaeger" Version="1.0.3" />
<PackageReference Include="MassTransit" Version="8.0.8" />
<PackageReference Include="MassTransit.RabbitMQ" Version="8.0.8" />
</ItemGroup>
服務註冊
將服務註冊到容器中,設置上報地址,注意此處上報地址是UDP類型,因此在雲伺服器中開安全組時需要是UDP類型
builder.Services.AddOpenTracing();
builder.Services.AddSingleton<ITracer>(serviceProvider =>
{
var serviceName = serviceProvider.GetRequiredService<IWebHostEnvironment>().ApplicationName;
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var sampler = new ConstSampler(sample: true);
var reporter = new RemoteReporter.Builder()
.WithLoggerFactory(loggerFactory)
.WithSender(new UdpSender("xxx.xxx.xxx.xxx", 6831, 0))
.Build();
var tracer = new Tracer.Builder(serviceName)
.WithLoggerFactory(loggerFactory)
.WithSampler(sampler)
.WithReporter(reporter)
.Build();
GlobalTracer.Register(tracer);
return tracer;
});
此處我在雲伺服器中開放6831的埠,注意是UDP
Http請求
在BFF處發起Http調用A服務,以及A服務發起Http調用B和C。
[HttpGet]
public async Task<string> GetAsync()
{
using var httpClient = _httpClientFactory.CreateClient();
httpClient.BaseAddress = new Uri("//localhost:7001");
var aServiceResult = await httpClient.GetStringAsync("/AValue");
return aServiceResult;
}
請求發送完畢,從Jaeger的儀錶面板查看監控數據,能夠看到一個請求的發起時間,所經過的服務數量、所調用服務的依賴關係、消耗的時長等資訊。整個請求鏈路也就看到了,B和C的同步請求,A和B,A和C的上下游請求也明了。
Jaeger提供了有向圖描述請求鏈路,來方便理清節點間的通訊邊界,整個請求鏈路也便清晰了。
事件驅動
在BFF處發起Http調用A服務,以及A服務往RabbitMQ發送集成事件。
[HttpPost]
public async Task<IActionResult> CreateAsync(string value)
{
var actionName = ControllerContext.ActionDescriptor.DisplayName;
using var scope = _tracer.BuildSpan(actionName).StartActive(finishSpanOnDispose: true);
var span = scope.Span.SetTag(Tags.SpanKind, Tags.SpanKindClient);
var dictionary = new Dictionary<string, string>();
_tracer.Inject(span.Context, BuiltinFormats.TextMap, new TextMapInjectAdapter(dictionary));
// Do something
// ...
// Send integration event
await _publishEndpoint.Publish(new ValueCreatedIntegrationEvent()
{
Value = value,
TrackingKeys = dictionary
});
return Ok();
}
D服務中消費集成事件,並寫入Sqlite庫中
public async Task Consume(ConsumeContext<ValueCreatedIntegrationEvent> context)
{
using var scope = TracingExtension.StartServerSpan(_tracer, context.Message.TrackingKeys, "Value created integration event handler");
var value = context.Message.Value;
Console.WriteLine($"Value:{value}");
await _dbContext.ValueAggregates.AddAsync(new ValueAggregate(value));
await _dbContext.SaveChangesAsync();
}
當請求發送完畢,事件消費完畢後,可以在Jaeger上看到在事件驅動下的鏈路調用過程,以及在調用過程中增加的tags和logs,寫入Sqlite的Sql。
在原有鏈路結構上,便又多了一個D服務。
參考
- //developer.aliyun.com/article/514488
- //www.cnblogs.com/wucy/p/13642289.html
- //www.cnblogs.com/catcher1994/p/10662999.html
2022-11-28,望技術有成後能回來看見自己的腳步