随着应用愈发复杂,请求的链路也愈发复杂,微服务化下,更是使得不同的服务分布在不同的机器,地域,语言也不尽相同。因此需要借助工具帮助分析,跟踪,定位请求中出现的若干问题,以此来保障服务治理,链路追踪也就出现了。
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网关层,以满足服务同步调用,服务间上下游调用,以及服务间事件通信。
为这几个服务设定期望如下
Nuget包引用
- <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("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
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服务。