隨著應用愈發複雜,請求的鏈路也愈發複雜,微服務化下,更是使得不同的服務分佈在不同的機器,地域,語言也不盡相同。因此需要藉助工具幫助分析,跟蹤,定位請求中出現的若干問題,以此來保障服務治理,鏈路追蹤也就出現了。
OpenTracing是一套分散式追蹤協定,與平臺,語言、廠商無關的Trace協定,統一介面,使得開發人員能夠方便的新增或更換更換不同的分散式追蹤系統。
同樣作為分散式追蹤協定的還有OpenCensus,以及兩者的合併體OpenTelemetry。
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提供了jaegertracing/all-in-one映象,搭建過程十分簡單,資料儲存在記憶體中,但需要注意容器掛了後資料就沒了。
docker run -d -p 6831:6831/udp -p 16686:16686 jaegertracing/all-in-one:latest
建立容器執行後,可以存取ip:16686檢視Jaeger的儀表面板
簡化大部分服務設計,整個結構上差不多是如下所示,服務層常見金字塔結構,服務上下游明確,以避免服務間的迴圈依賴。
此處建立四個服務以及一個BFF閘道器層,以滿足服務同步呼叫,服務間上下游呼叫,以及服務間事件通訊。
為這幾個服務設定期望如下
<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
在BFF處發起Http呼叫A服務,以及A服務發起Http呼叫B和C。
[HttpGet]
public async Task<string> GetAsync()
{
using var httpClient = _httpClientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://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服務。
2022-11-28,望技術有成後能回來看見自己的腳步