微服务架构之分布式追踪(.net6集成Zipkin)

  在微服务架构中,由于服务之间做了拆分,一次请求往往要涉及多个服务的调用,不同的服务可能由不同的团队开发,使用不同的编程语言,还有可能部署在不同的机器上,分布在不同的数据中心。当请求出现异常问题时,我们需要知道本次请求调用了哪些服务,又是哪个服务引起的错误,如果靠人肉的方式,到每个服务去查找代码,查看日志,那么无疑是十分痛苦的,这时候分布式追踪系统应运而生。

服务追踪的作用

  除了能够快速定位请求失败的原因之外,服务追踪系统还需要记录调用经过的每一条链路上的耗时,这样在我们分析追踪数据时,就可以快速定位系统的瓶颈在哪里,针对耗时较长的链路,做出针对性的优化。通过追踪系统的链路可视化功能,可以更直观的看到请求的调用链路,及服务之间的依赖关系,我们可以凭此评估链路的调用是否合理,优化链路调用。同时链路追踪系统大多都集成了 生成系统调用关系网络拓扑的功能,它可以将整套系统间的服务调用关系完整的,直观的展示出来,通过网络拓扑可以更好的优化系统架构,找出系统层级间不合理的地方。

服务追踪的原理

  要理解服务追踪的原理,首先必须搞懂一些基本概念:traceId、spanId、annonation 等。

  

  • traceId:用于标识某一次具体的请求 ID。当用户的请求进入系统后,会在 RPC 调用网络的第一层生成一个全局唯一的 traceId,并且会随着每一层的 RPC 调用,不断往后传递,这样的话通过 traceId 就可以把一次用户请求在系统中调用的路径串联起来。    
  • spanId:用于标识一次 RPC 调用在分布式请求中的位置。当用户的请求进入系统后,处在 RPC 调用网络的第一层 A 时 spanId 初始值是 0,进入下一层 RPC 调用 B 的时候 spanId 是 0.1,继续进入下一层 RPC 调用 C 时 spanId 是 0.1.1,而与 B 处在同一层的 RPC 调用 E 的 spanId 是 0.2,这样的话通过 spanId 就可以定位某一次 RPC 请求在系统调用中所处的位置,以及它的上下游依赖分别是谁。
  • annotation:用于业务自定义埋点数据,可以是业务感兴趣的想上传到后端的数据,比如一次请求的用户 UID。

  简单的总结一下,traceId 是用于串联某一次请求在系统中经过的所有路径,spanId 是用于区分系统不同服务之间调用的先后关系,而 annotation 是用于业务自定义一些自己感兴趣的数据,在上传 traceId 和 spanId 这些基本信息之外,添加一些自己感兴趣的信息。

 开源服务追踪系统对比

  Google的Dapper,阿里的鹰眼,大众点评的CAT,Twitter的Zipkin,LINE的pinpoint,国产的skywalking。市面上的全链路监控理论模型大多都是借鉴Google Dapper论文,本文重点关注以下两种APM组件:

  • Zipkin :由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微服务架构中的延迟问题,包括:数据的收集、存储、查找和展现。
  • Skywalking :国产的优秀APM组件,是一个对JAVA分布式应用程序集群的业务运行情况进行追踪、告警和分析的系统。

类型

zipkin

SKYwalking

基本原理

拦截请求,发送(HTTP,mq)数据至zipkin服务

java探针,字节码增强

接入方式

基于linkerd或者sleuth方式,引入配置即可

avaagent字节码

支持OpenTracing

颗粒度

接口级(类级别)

方法级

存储

ES,mysql,Cassandra,内存

ES,H2,TIDB

agent到collector的协议

http,MQ

http,gRPC

  对代码无任何侵入,除了性能和对代码的侵入性上 SkyWaking 表现不错外,它还有以下优势,对多语言的支持,组件丰富:目前其支持 Java, .Net Core, PHP, NodeJS, Golang, LUA 语言,组件上也支持dubbo, mysql 等常见组件,

Zipkin的Docker部署

docker run --name zipkin -d -p 9411:9411  openzipkin/zipkin

  执行以上命令,zipkin会按照默认的情况将数据存储在内存中,容器重启后,数据就会丢失,可以在开发或测试环境用此种方式,如果是生产环境还是建议将底层存储换成Elasticsearch,可以保证数据的持久化存储。

docker run --name zipkin -d -p 9411:9411 -e STORAGE_TYPE=elasticsearch -e ES_HOSTS=192.168.0.8:9200 openzipkin/zipkin

  值得一提的是,底层存储更换成ES后,网络拓扑图将不会自动生成,需要启动下面的容器生成网络拓扑(注意:它是一次性服务,需要定时任务执行此容器,或在需要时手动启动它,才能看到最新的网络拓扑)。

docker run --name zipkin-dependencies --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.0.8:9200 --env ES_INDEX=zipkin --rm=true -e JAVA_OPTS="-Xmx3550m -Xms3550m" openzipkin/zipkin-dependencies

.net6集成Zipkin

  首先需要引入如下依赖包  

 

 

 

 

 

 

  此处使用了 System.Diagnostic.DiagnosticListener 实现对应用程序的监听,具体的订阅原理可以进入链接中的文章去了解和学习。

1 public interface ITraceDiagnosticListener
2 {
3     string DiagnosticName { get; }
4 }
ITraceDiagnosticListener
 1 public class HttpDiagnosticListener : ITraceDiagnosticListener
 2 {
 3     public string DiagnosticName => "HttpHandlerDiagnosticListener";
 4 
 5     private ClientTrace clientTrace;
 6     private readonly IInjector<HttpHeaders> _injector = Propagations.B3String.Injector<HttpHeaders>((carrier, key, value) => carrier.Add(key, value));
 7     private readonly IConfiguration _configuration;
 8     private  ZipkinOptions _zipkinOptions = new ZipkinOptions();
 9 
10     public HttpDiagnosticListener(IConfiguration configuration)
11     {
12         _configuration = configuration;
13         _configuration.GetRequiredSection("Zipkin").Bind(_zipkinOptions);
14     }
15 
16     [DiagnosticName("System.Net.Http.Request")]
17     public void HttpRequest(HttpRequestMessage request)
18     {
19         clientTrace = new ClientTrace(_zipkinOptions.ServiceName, request.Method.Method);
20         if (clientTrace.Trace != null && request != null)
21         {
22             _injector.Inject(clientTrace.Trace.CurrentSpan, request.Headers);
23         }
24     }
25 
26     [DiagnosticName("System.Net.Http.Response")]
27     public void HttpResponse(HttpResponseMessage response)
28     {
29         if (clientTrace.Trace != null && response != null)
30         {
31             clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_PATH, response.RequestMessage.RequestUri.LocalPath));
32             clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_METHOD, response.RequestMessage.Method.Method));
33             clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_HOST, response.RequestMessage.RequestUri.Host));
34             if (!response.IsSuccessStatusCode)
35             {
36                 clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_STATUS_CODE, ((int)response.StatusCode).ToString()));
37             }
38         }
39     }
40 
41     [DiagnosticName("System.Net.Http.Exception")]
42     public void HttpException(HttpRequestMessage request, Exception exception)
43     {
44     }
45 }
HttpDiagnosticListener
 1 public class TraceObserver : IObserver<DiagnosticListener>
 2 {
 3     private IEnumerable<ITraceDiagnosticListener> _traceDiagnostics;
 4     public TraceObserver(IEnumerable<ITraceDiagnosticListener> traceDiagnostics)
 5     {
 6         _traceDiagnostics = traceDiagnostics;
 7     }
 8 
 9     public void OnCompleted()
10     {
11     }
12 
13     public void OnError(Exception error)
14     {
15     }
16 
17     public void OnNext(DiagnosticListener listener)
18     {
19         var traceDiagnostic = _traceDiagnostics.FirstOrDefault(i => i.DiagnosticName == listener.Name);
20         if (traceDiagnostic != null)
21         {
22             //适配订阅
23             listener.SubscribeWithAdapter(traceDiagnostic);
24         }
25     }
26 }
TraceObserver
  集成zipkin的注入及中间件添加的方式,可以封装为nuget包的形式,方便其它服务使用。
 1 public class ZipkinOptions
 2 {
 3     public string ServiceName { get; set; }
 4 
 5     public string Url { get; set; }
 6 
 7 }
 8 
 9 public static class ZipkinExtensions
10 {
11     public static IServiceCollection AddZipkin(this IServiceCollection services)
12     {
13         services.AddSingleton<ITraceDiagnosticListener, HttpDiagnosticListener>();
14         return services.AddSingleton<TraceObserver>();
15     }
16 
17     public static IApplicationBuilder UseZipkin(this IApplicationBuilder app, IHostApplicationLifetime lifetime)
18     {
19         var configuration = app.ApplicationServices.GetRequiredService<IConfiguration>();
20         var zipkinOptions = new ZipkinOptions();
21         configuration.GetSection("Zipkin").Bind(zipkinOptions);
22         return UseZipkin(app, lifetime, zipkinOptions.ServiceName, zipkinOptions.Url);
23     }
24 
25     public static IApplicationBuilder UseZipkin(this IApplicationBuilder app, IHostApplicationLifetime lifetime, string serviceName, string zipkinUrl)
26     {
27         ILoggerFactory loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
28         var traceObserver = app.ApplicationServices.GetService<TraceObserver>();
29         if (traceObserver != null)
30         {
31             DiagnosticListener.AllListeners.Subscribe(traceObserver);
32         }
33         lifetime.ApplicationStarted.Register(() =>
34         {
35             TraceManager.SamplingRate = 1.0f;//记录数据密度,1.0代表全部记录
36             var logger = new TracingLogger(loggerFactory, "zipkin4net");
37             var httpSender = new HttpZipkinSender(zipkinUrl, "application/json");
38 
39             var tracer = new ZipkinTracer(httpSender, new JSONSpanSerializer(), new Statistics());
40             var consoleTracer = new zipkin4net.Tracers.ConsoleTracer();
41 
42 
43             TraceManager.RegisterTracer(tracer);
44             TraceManager.RegisterTracer(consoleTracer);
45             TraceManager.Start(logger);
46 
47         });
48         lifetime.ApplicationStopped.Register(() => TraceManager.Stop());
49         app.UseTracing(serviceName);//这边的名字可自定义
50         return app;
51     }
52 }
ZipkinExtensions
  应用启动项添加zipkin中间件,并在配置文件中配置服务名称及zipkin地址。
1 var builder = WebApplication.CreateBuilder(args);
2 builder.Services.AddZipkin();
3 builder.Services.AddControllers();
4 builder.Services.AddEndpointsApiExplorer();
5 
6 var app = builder.Build();
7 app.UseZipkin(app.Lifetime);
8 app.MapControllers();
9 app.Run();
Program

可视化展示

  访问主机的9411端口,就可以进入zipkin系统。以下是实际的使用效果。

  单次调用的链路追踪

  网络拓扑

posted @ 2022-08-26 19:14  唐磊(Jason)  阅读(292)  评论(0编辑  收藏  举报