使用zipKin构建NetCore分布式链路跟踪

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用zipKin构建NetCore分布式链路跟踪相关的知识,希望对你有一定的参考价值。

本文主要讲解使用ZipKin构建NetCore分布式链路跟踪


场景

因为最近公司业务量增加,而项目也需要增大部署数量,K8S中Pod基本都扩容了一倍,新增了若干物理机,部分物理机网络通信存在问题,导致部分请求打入有问题的物理机时总会出现超时的情况,由于之前系统中没有使用链路跟踪,导致排查问题比较慢,所以就去研究了市面上的链路框架,结果发现了ZipKin这款比较轻量级的链路跟踪框架。


实例代码

本文日志系统采用Exceplesstion
示例代码请求链路为SimpleZipkin(网关服务)--->WebApi(Api服务)--->OrderApi(订单服务)
首先创建公用类库,引用以下包(本文以1.5.0版本为例)
如果部署Zipkin使用的是mysql作为存储,切记Mysql版本不要高于8.0,Zipkin暂不支持8.0的版本

zipkin4net 
zipkin4net.middleware.aspnetcore

创建ZipKin帮助类

public static class ZipKinExtensions
    {
        public static IServiceCollection AddZipKin(this IServiceCollection services)
        {
            return services.AddSingleton<HttpDiagnosticSourceObserver >();
        }

        public static IApplicationBuilder UseZipKin(this IApplicationBuilder app, IHostApplicationLifetime lifetime, ILoggerFactory loggerFactory, string serviceName, string zipKinUrl)
        {
            DiagnosticListener.AllListeners.Subscribe(app?.ApplicationServices?.GetService<TraceObserver>());
            lifetime.ApplicationStarted.Register(() =>
            {
                TraceManager.SamplingRate = 1.0f;//记录数据密度,1.0代表全部记录
                var logger = new TracingLogger(loggerFactory, "zipkin4net");
                var httpSender = new HttpZipkinSender(zipKinUrl, "application/json");
                var tracer = new ZipkinTracer(httpSender, new JSONSpanSerializer(), new Statistics());
                var consoleTracer = new zipkin4net.Tracers.ConsoleTracer();
                TraceManager.RegisterTracer(tracer);
                TraceManager.RegisterTracer(consoleTracer);
                TraceManager.Start(logger);

            });
            lifetime.ApplicationStopped.Register(() => TraceManager.Stop());
            app.UseTracing(serviceName);//这边的名字可自定义
            return app;
        }
    }

Exceptionless帮助类

/// <summary>
    /// 日志扩展类
    /// </summary>
    public static class LogHelper
    {
        /// <summary>
        /// 记录Info日志
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="message"></param>
        public static void InformationToException(this ILogger logger, string message)
        {
            var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
            logger.LogInformation($@"tranceId={tranceId},日志主体为:{message}");
        }

        /// <summary>
        /// 记录Debug日志
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="message"></param>
        public static void DebugToException(this ILogger logger, string message)
        {
            var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
            logger.LogDebug($@"tranceId={tranceId},日志主体为:{message}");
        }

        /// <summary>
        /// 记录错误日志
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="message"></param>
        public static void ErrorToException(this ILogger logger, string message)
        {
            var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
            logger.LogError($@"tranceId={tranceId},日志主体为:{message}");
        }

        /// <summary>
        /// 记录追踪日志
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="message"></param>
        public static void TraceToException(this ILogger logger, string message)
        {
            var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
            logger.LogTrace($@"tranceId={tranceId},日志主体为:{message}");
        }

        /// <summary>
        /// 记录警告日志
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="message"></param>
        public static void WarningToException(this ILogger logger, string message)
        {
            var tranceId = Trace.Current?.CurrentSpan.TraceId.ToString("x16");
            logger.LogWarning($@"tranceId={tranceId},日志主体为:{message}");
        }
    }

接下来创建SimpleZipkin、WebApi、OrderApi等项目(因为结构一致,所以本文只创建一个),首先引用Exceplesstion

public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging((hostContext, configLogging) =>
                {
                    configLogging.AddConfiguration(hostContext.Configuration.GetSection("Logging"));
                    configLogging.AddConsole();
                    configLogging.AddDebug();
                    configLogging.AddExceptionless();
                    ExceptionlessClient.Default.Configuration.SetDefaultMinLogLevel(Exceptionless.Logging.LogLevel.Debug);
                    configLogging.SetMinimumLevel(LogLevel.Debug);                   
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

接下来在Startup中引入ZipKin

public void ConfigureServices(IServiceCollection services)
        {
            // 注入Rpc
            //AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
            //services.AddGrpcClient<HelloServer.HelloServerClient>((p, o) =>
            //{
            //    o.Address = new Uri("http://127.0.0.1:3848");
            //});
            //.AddHttpMessageHandler(provider => TracingHandler.WithoutInnerHandler("RpcService"));
            services.AddControllers();
            services.AddZipKin();
            services.AddSingleton<IDiagnosticSource, HttpDiagnosticSourceDemo>();
            services.AddHttpClient("webApi", client => { client.BaseAddress = new Uri($"http://localhost:5001"); });
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "SimpleZipKin", Version = "v1" });
            });
        }


   public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory,
            IHostApplicationLifetime lifetime)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SimpleZipkin v1"));
            }
            Configuration.GetSection("ExceptionLess").Bind(ExceptionlessClient.Default.Configuration);
            ExceptionlessClient.Default.Configuration.SetDefaultMinLogLevel(Exceptionless.Logging.LogLevel.Debug);
            app.UseZipKin(lifetime, loggerFactory, "SimpleZip", "http://127.0.0.1:9411");//SimpleZip修改为对应的应用名称,127.0.0.1地址切换为自己的zipkin地址  
            app.UseRouting();

            app.UseAuthorization();
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
        }

接下来创建对应的Controller

[Route("/api/Home")]
    public class HomeController : Controller
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ILogger _logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="httpClientFactory"></param>
        /// <param name="logger"></param>
        public HomeController(IHttpClientFactory httpClientFactory, ILogger<HomeController> logger)
        {
            _httpClientFactory = httpClientFactory;
            //_helloServerClient = helloServerClient;
            _logger = logger;
        }

        [HttpGet("GetZipKin")]
        public async Task<string> GetZipKin()
        {
            _logger.InformationToException($@"这里是SimpleZipKinApi");
            var httpClient = _httpClientFactory.CreateClient("webApi");
            var httpResult = await httpClient.GetAsync($"api/order/getorder");
            var result = await httpResult.Content.ReadAsStringAsync();
            return result;
        }    
    }

最后在appsettings.json中加入对应的Exceplesstionless配置

"ExceptionLess": {
    "ApiKey": "****************************",
    "ServerUrl": "http://127.0.0.1:5000"
  }

OrderApi、WebApi如法炮制,修改对应的请求链路信息

接下来我们使用DiagnosticAdapter做链路记载,在公共类库中创建HttpDiagnosticListener类
DiagnosticSource是Runtime层提供,应用层可以通过它与系统集成、事件日志、以及性能计数器进行交互。
DiagnosticSource官方介绍:https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.diagnosticsource?view=net-5.0
关于DiagnosticSource设计参考原有yi念之间大佬的文章:https://www.cnblogs.com/wucy/p/13532534.html

public class HttpDiagnosticSourceDemo : IDiagnosticSourceDemo
    {
        public string DiagnosticName => "HttpDiagnosticSourceDemo";

        private ClientTrace _clientTrace;
        private readonly IInjector<HttpHeaders> _injector = Propagations.B3String.Injector<HttpHeaders>((carrier, key, value) => carrier.Add(key, value));

        [DiagnosticName("System.Net.Http.Request")]
        public void HttpRequest(HttpRequestMessage request)
        {
            _clientTrace = new ClientTrace("simpleZipKin", request.Method.Method);
            if (_clientTrace.Trace != null)
            {
                _injector.Inject(_clientTrace.Trace.CurrentSpan, request.Headers);
            }
        }

        [DiagnosticName("System.Net.Http.Response")]
        public void HttpResponse(HttpResponseMessage response)
        {
            if (_clientTrace.Trace != null)
            {
                _clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_PATH, response.RequestMessage.RequestUri.LocalPath));
                _clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_METHOD, response.RequestMessage.Method.Method));
                _clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_HOST, response.RequestMessage.RequestUri.Host));
                if (!response.IsSuccessStatusCode)
                {
                    _clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_STATUS_CODE, ((int)response.StatusCode).ToString()));
                }
            }
        }

        [DiagnosticName("System.Net.Http.Exception")]
        public void HttpException(HttpRequestMessage request, Exception exception)
        {
        }
    }

IDiagnosticSourceDemo接口信息如下:

public interface IDiagnosticSourceDemo
    {
        string DiagnosticName { get; }
    }

HttpDiagnosticSourceObserver方法如下:

public class HttpDiagnosticSourceObserver : IObserver<DiagnosticListener>
    {
        private IEnumerable<IDiagnosticSourceDemo> _diagnosticSourceDemo;
        public HttpDiagnosticSourceObserver (IEnumerable<IDiagnosticSourceDemo> diagnosticSourceDemo)
        {
            _diagnosticSourceDemo= diagnosticSourceDemo;
        }

        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(DiagnosticListener listener)
        {
            var diagnosticSource= _diagnosticSourceDemo.FirstOrDefault(i => i.DiagnosticName == listener.Name);
            if (traceDiagnostic != null)
            {
                //适配订阅
                listener.SubscribeWithAdapter(diagnosticSource);
            }
        }
    }

最终运行结果如下:
Zipkin为:

Exceplesstion日志记录信息为

通过Exceptionless记录的环境信息也能将对应的机器定位

参考

本文引用文章连接如下:
DiagnosticSource官方介绍:https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.diagnosticsource?view=net-5.0
DiagnosticSource参考连接:https://www.cnblogs.com/wucy/p/13532534.html
                   https://sudonull.com/post/3671-Using-the-DiagnosticSource-in-NET-Core-Theory
                   https://andrewlock.net/logging-using-diagnosticsource-in-asp-net-core/
docker部署Zipkin:https://www.cnblogs.com/binz/p/12658020.html
docekr部署exceptionless:https://www.cnblogs.com/edisonchou/p/exceptionless_v5_deployment_introduction.html

以上是关于使用zipKin构建NetCore分布式链路跟踪的主要内容,如果未能解决你的问题,请参考以下文章

分布式链路跟踪sleuth(zipkin+kafka+elasticsearch)

spring cloud 分布式链路跟踪(集成zipkin)

spring cloud分布式整合zipkin的链路跟踪

Spring Cloud 分布式链路跟踪 Sleuth + Zipkin + Elasticsear

Spring Cloud 分布式链路跟踪 Sleuth + Zipkin + Elasticsear

跟我学SpringCloud | 第十一篇:使用Spring Cloud Sleuth和Zipkin进行分布式链路跟踪