海港区网站快排seo,怎么优化网站打开速度,夏津网站建设价格,wordpress 登录logo前言在日常使用ASP.NET Core的开发或学习中#xff0c;如果有需要使用链路跟踪系统#xff0c;大多数情况下会优先选择SkyAPM。我们之前也说过SkyAPM设计确实比较优秀#xff0c;巧妙的利用DiagnosticSource诊断跟踪日志#xff0c;可以做到对项目无入侵方式的集成。其实还… 前言 在日常使用ASP.NET Core的开发或学习中如果有需要使用链路跟踪系统大多数情况下会优先选择SkyAPM。我们之前也说过SkyAPM设计确实比较优秀巧妙的利用DiagnosticSource诊断跟踪日志可以做到对项目无入侵方式的集成。其实还有一款比较优秀的链路跟踪系统也可以支持ASP.NET Core叫Zipkin。它相对于SkyWalking来说相对轻量级使用相对来说比较偏原生的方式而且支持Http的形式查询和提交链路数据。因为我们总是希望能拥有多一种的解决方案方便对比和参考所以接下来我们就来学习一下关于Zipkin的使用方式。Zipkin简介 Zipkin是由Twitter开源的一款基于Java语言开发的分布式实时数据追踪系统(Distributed Tracking System),其主要功能是采集来自各个系统的实时监控数据。该系统让开发者可通过一个 Web 前端轻松的收集和分析数据例如用户每次请求服务的处理时间等可方便的监测系统中存在的瓶颈。它大致可以分为三个核心概念首先是上报端它主要通过代码的形式集成到程序中用于上报Trace数据到Collector端。Collector负责接收客户端发送过来的数据保存到内存或外部存储系统中供UI展示。存储端可以是基于zipkin内存完全不依赖外部存储的In-Memory形式或依赖外部存储系统的形式一般采用外部存储系统存储链路数据毕竟内存有限。它可支持的存储数据库有MySQL、Cassandra、Elasticsearch。UI负责展示采集的链路数据及系统之间的依赖关系。相对来说还是比较清晰的如果用一张图表示整体架构的话大致如下图所示(图片来源于网络)在学习链路跟踪的过程中会设计到相关概念我们接下来介绍链路跟踪几个相关的概念TranceId,一般一次全局的请求会有一个唯一的TraceId用于代表一次唯一的请求。比如我请求了订单管理系统而订单管理系统内部还调用了商品管理系统而商品管理系统还调用了缓存系统或数据库系统。但是对全局或外部来说这是一次请求所以会有唯一的一个TraceId。SpanId,虽然全局的来说是一次大的请求但是在这个链路中内部间还会发起别的请求这种内部间的每次请求会生成一个SpanId。如果将整条链路串联起来的话我们需要记录全局的TraceId代表当前节点的SpanId和发起对当前节点调用的的父级ParentId。然后基于链路跟踪的核心概念然后介绍一下Zipkin衍生出来了几个相关概念cs:Clent Sent 客户端发起请求的时间比如 dubbo 调用端开始执行远程调用之前。cr:Client Receive 客户端收到处理完请求的时间。ss:Server Receive 服务端处理完逻辑的时间。sr:Server Receive 服务端收到调用端请求的时间。sr - cs 请求在网络上的耗时
ss - sr 服务端处理请求的耗时
cr - ss 回应在网络上的耗时
cr - cs 一次调用的整体耗时
关于zipkin概念相关的就介绍这么多接下来我们介绍如何部署Zipkin。部署ZipKin 关于Zipkin常用的部署方式大概有两种一种是通过下载安装JDK然后运行zipkin.jar的方式另一种是基于Docker的方式。为了方便我采用的是基于Docker的方式部署因为采用原生的方式去部署还需要安装JDK而且操作相对比较麻烦。咱们上面说过虽然Zipkin可以将链路数据存放到内存中但是这种操作方式并不实用实际使用过程中多采用ElasticSearch存储链路数据。所以部署的时候需要依赖Zipkin和ElasticSearch对于这种部署形式采用docker-compose的方式就再合适不过了大家可以在Zipkin官方Github中找到docker的部署方式地址是https://github.com/openzipkin/zipkin/tree/master/docker,官方使用的方式相对比较复杂下载下来docker-compose相关文件之后我简化了它的使用方式最终修改如下version: 3.6
services:elasticsearch:# 我使用的是7.5.0版本image: elasticsearch:7.5.0container_name: elasticsearchrestart: always#暴露es端口ports:- 9200:9200environment:- discovery.typesingle-node- bootstrap.memory_locktrue#es有内存要求- ES_JAVA_OPTS-Xms512m -Xmx512mulimits:memlock:soft: -1hard: -1networks:default:aliases:- elasticsearchzipkin:image: openzipkin/zipkincontainer_name: zipkinrestart: alwaysnetworks:default:aliases:- zipkinenvironment:#存储类型为es- STORAGE_TYPEelasticsearch#es地址- ES_HOSTSelasticsearch:9200ports:- 9411:9411#依赖es所以在es启动完成后在启动zipkindepends_on:- elasticsearch
通过docker-compose运行编辑后的yaml文件,一条指令就可以运行起来docker-compose -f docker-compose-elasticsearch7.yml up
其中-f是指定文件名称如果是docker-compose.yml则可以直接忽略文件名称当shell中出现如下界面并且在浏览器中输入http://localhost:9411/zipkin/出现如图所示则说明Zikpin启动成功整合ASP.NET CoreZipKin启动成功之后我们就可以将程序中的数据采集到Zipkin中去了我新建了两个ASP.NET Core的程序一个是OrderApi,另一个是ProductApi方便能体现出调用链路其中OrderApi调用ProductApi接口在两个项目中分别引入Zipkin依赖包PackageReference Includezipkin4net Version1.5.0 /
PackageReference Includezipkin4net.middleware.aspnetcore Version1.5.0 /
其中zipkin4net为核心包zipkin4net.middleware.aspnetcore是集成ASP.NET Core的程序包。然后我们在Startup文件中添加如下方法public void RegisterZipkinTrace(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostApplicationLifetime lifetime)
{lifetime.ApplicationStarted.Register(() {//记录数据密度1.0代表全部记录TraceManager.SamplingRate 1.0f;//链路日志var logger new TracingLogger(loggerFactory, zipkin4net);//zipkin服务地址和内容类型var httpSender new HttpZipkinSender(http://localhost:9411/, 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());//引入zipkin中间件用于跟踪服务请求,这边的名字可自定义代表当前服务名称app.UseTracing(Configuration[nacos:ServiceName]);
}
然后我们在Configure方法中调用RegisterZipkinTrace方法即可。由于我们要在OrderApi项目中采用HttpClient的方式调用ProductAPI,默认zipkin4net是支持采集HttpClient发出请求的链路数据(由于在ProductApi中我们并不发送Http请求所以可以不用集成一下操作)具体集成形式如下如果使用的是HttpClientFactory的方式在ConfigureServices中配置如下public void ConfigureServices(IServiceCollection services)
{//由于我使用了Nacos作为服务注册中心services.AddNacosAspNetCore(Configuration);services.AddScopedNacosDiscoveryDelegatingHandler();services.AddHttpClient(ServiceName.ProductService,client {client.BaseAddress new Uri($http://{ServiceName.ProductService});}).AddHttpMessageHandlerNacosDiscoveryDelegatingHandler()//引入zipkin trace跟踪httpclient请求名称配置当前服务名称即可.AddHttpMessageHandler(provider TracingHandler.WithoutInnerHandler(Configuration[nacos:ServiceName]));services.AddControllers();
}
如果是直接是使用HttpClient的形式调用则可以采用以下方式using (HttpClient client new HttpClient(new TracingHandler(OrderApi)))
{
}
然后我们在OrderApi中写一段调用ProductApi的代码[Route(orderapi/[controller])]
public class OrderController : ControllerBase
{private ListOrderDto orderDtos new ListOrderDto();private readonly IHttpClientFactory _clientFactory;public OrderController(IHttpClientFactory clientFactory){orderDtos.Add(new OrderDto { Id 1, TotalMoney222,Address北京市,Addresseeme,From淘宝,SendAddress武汉 });_clientFactory clientFactory;}/// summary/// 获取订单详情接口/// /summary/// param nameid订单id/param/// returns/returns[HttpGet(getdetails/{id})]public async TaskOrderDto GetOrderDetailsAsync(long id){OrderDto orderDto orderDtos.FirstOrDefault(i i.Id id);if (orderDto ! null){OrderDetailDto orderDetailDto new OrderDetailDto{Id orderDto.Id,TotalMoney orderDto.TotalMoney,Address orderDto.Address,Addressee orderDto.Addressee,From orderDto.From,SendAddress orderDto.SendAddress};//调用ProductApi服务接口var client _clientFactory.CreateClient(ServiceName.ProductService);var response await client.GetAsync($/productapi/product/getall);var result await response.Content.ReadAsStringAsync();orderDetailDto.Products JsonConvert.DeserializeObjectListOrderProductDto(result);return orderDetailDto;}return orderDto;}
}
在ProductApi中我们只需要编写调用RegisterZipkinTrace方法即可和OrderApi一样我们就不重复粘贴了。因为ProductApi不需要调用别的服务所以可以不必使用集成HttpClient,只需要提供简单的接口即可[Route(productapi/[controller])]
public class ProductController : ControllerBase
{private ListProductDto productDtos new ListProductDto();public ProductController(){productDtos.Add(new ProductDto { Id 1,Name酒精,Price22.5m });productDtos.Add(new ProductDto { Id 2, Name 84消毒液, Price 19.9m });}/// summary/// 获取所有商品信息/// /summary/// returns/returns[HttpGet(getall)]public IEnumerableProductDto GetAll(){return productDtos;}
}
启动这两个项目调用OrderApi的getdetails接口完成后打开zipkin界面点击进去可查看链路详情总结起来核心操作其实就两个一个是在发送请求的地方使用TracingHandler记录发起端的链路情况然后在接收请求的服务端使用UseTracing记录来自于客户端请求的链路情况。改进集成方式 其实在上面的演示中我们可以明显的看到明显的不足就是很多时候其实我们没办法去设置HttpClient相关的参数的很多框架虽然也是使用的HttpClient或HttpClientFactory相关但是在外部我们没办法通过自定义的方式去设置他们的相关操作比如Ocelot其实也是使用HttpClient相关发起的转发请求但是对外我们没办法通过我们的程序去设置HttpClient的参数。还有就是在.Net Core中WebRequest其实也是对HttpClient的封装但是我们同样没办法在我们的程序中给他们传递类似TracingHandler的操作。现在我们从TracingHandler源码开始解读看看它的内部到底是如何工作的zipkin官方提供的.net core插件zipkin4net的源码位于https://github.com/openzipkin/zipkin4net,我们找到TracingHandler类所在的位置[点击查看源码????]由于TracingHandler本身就是DelegatingHandler的子类所以我们主要看SendAsync方法大致抽离出来如下protected override async TaskHttpResponseMessage SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{FuncHttpRequestMessage, string _getClientTraceRpc _getClientTraceRpc getClientTraceRpc ?? (request request.Method.ToString());IInjectorHttpHeaders _injector Propagations.B3String.InjectorHttpHeaders((carrier, key, value) carrier.Add(key, value));//记录发起请求客户端链路信息的类是ClientTraceusing (var clientTrace new ClientTrace(_serviceName, _getClientTraceRpc(request))){if (clientTrace.Trace ! null){_injector.Inject(clientTrace.Trace.CurrentSpan, request.Headers);}var result await clientTrace.TracedActionAsync(base.SendAsync(request, cancellationToken));//AddAnnotation是记录标签信息我们可以在zipkin链路详情中看到这些标签if (clientTrace.Trace ! null){//记录请求路径clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_PATH, result.RequestMessage.RequestUri.LocalPath));//记录请求的http方法clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_METHOD, result.RequestMessage.Method.Method));if (_logHttpHost){//记录主机clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_HOST, result.RequestMessage.RequestUri.Host));}if (!result.IsSuccessStatusCode){clientTrace.AddAnnotation(Annotations.Tag(zipkinCoreConstants.HTTP_STATUS_CODE, ((int)result.StatusCode).ToString()));}}return result;}
}
实现方式比较简单就是借助ClientTrace记录一些标签其他的相关操作都是由zipkin4net提供的。我们在之前的文章.Net Core中的诊断日志DiagnosticSource讲解中层说道HttpClient底层会有发出诊断日志我们可以借助这个思路来对HttpClient进行链路跟踪埋点。我们结合Microsoft.Extensions.DiagnosticAdapter扩展包定义如下类public class HttpDiagnosticListener: ITraceDiagnosticListener
{public string DiagnosticName HttpHandlerDiagnosticListener;private ClientTrace clientTrace;private readonly IInjectorHttpHeaders _injector Propagations.B3String.InjectorHttpHeaders((carrier, key, value) carrier.Add(key, value));[DiagnosticName(System.Net.Http.Request)]public void HttpRequest(HttpRequestMessage request){clientTrace new ClientTrace(apigateway, 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){}
}
ITraceDiagnosticListener是我们方便操作DiagnosticListener定义的接口接口仅包含DiagnosticName用来表示DiagnosticListener监听的名称有了这个接口接下来的操作我们会方便许多接下来我们来看订阅操作的实现。public class TraceObserver :IObserverDiagnosticListener
{private IEnumerableITraceDiagnosticListener _traceDiagnostics;public TraceObserver(IEnumerableITraceDiagnosticListener traceDiagnostics)
{_traceDiagnostics traceDiagnostics;}public void OnCompleted()
{}public void OnError(Exception error)
{}public void OnNext(DiagnosticListener listener)
{//这样的话我们可以更轻松的扩展其他DiagnosticListener的操作var traceDiagnostic _traceDiagnostics.FirstOrDefault(ii.DiagnosticNamelistener.Name);if (traceDiagnostic!null){//适配订阅listener.SubscribeWithAdapter(traceDiagnostic);}}
}
通过这种操作我们就无需关心如何将自定义的DiagnosticListener订阅类适配到DiagnosticAdapter中去方便我们自定义其他DiagnosticListener的订阅类这样的话我们只需注册自定义的订阅类即可。services.AddSingletonTraceObserver();
services.AddSingletonITraceDiagnosticListener, HttpDiagnosticListener();
通过这种改进方式我们可以解决类似HttpClient封装到框架中并且我们我们无法通过外部程序去修改设置的时候。比如我们在架构中引入了Ocelot网关我们就可以采用类似这种方式在网关层集成zipkin4net。自定义埋点 通过上面我们查看TracingHandler的源码我们得知埋点主要是通过ClientTrace进行的它是在发起请求的客户端进行埋点。在服务端埋点的方式我们可以通过TracingMiddleware中间件中的源码查看到[点击查看源码????]叫ServerTrace。有了ClientTrace和ServerTrace我们可以非常轻松的实现一次完整的客户端和服务端埋点只需要通过它们打上一些标签即可。其实它们都是对Trace类的封装我们找到它们的源码进行查看public class ClientTrace : BaseStandardTrace, IDisposable
{public ClientTrace(string serviceName, string rpc){if (Trace.Current ! null){Trace Trace.Current.Child();}Trace.Record(Annotations.ClientSend());Trace.Record(Annotations.ServiceName(serviceName));Trace.Record(Annotations.Rpc(rpc));}public void Dispose(){Trace.Record(Annotations.ClientRecv());}
}public class ServerTrace : BaseStandardTrace, IDisposable
{public override Trace Trace{get{return Trace.Current;}}public ServerTrace(string serviceName, string rpc){Trace.Record(Annotations.ServerRecv());Trace.Record(Annotations.ServiceName(serviceName));Trace.Record(Annotations.Rpc(rpc));}public void Dispose(){Trace.Record(Annotations.ServerSend());}
}
因此如果你想通过更原始的方式去记录跟踪日志可以采用如下方式var trace Trace.Create();
trace.Record(Annotations.ServerRecv());
trace.Record(Annotations.ServiceName(serviceName));
trace.Record(Annotations.Rpc(GET));
trace.Record(Annotations.ServerSend());
trace.Record(Annotations.Tag(http.url, url));
示例Demo由于上面说的比较多而且有一部分关于源码的解读为了防止由本人文笔有限给大家带来理解误区另一方面也为了更清晰的展示Zipkin的集成方式我自己做了一套Demo目录结构如下ApiGateway为网关项目可以转发针对OrderApi的请求OrderApi和ProductApi用于模拟业务系统这三个项目都集成了zipkin4net链路跟踪他们之间是通过Nacos实现服务的注册和发现。这个演示Demo我本地是可以直接运行成功的如果有下载下来运行不成功的可以评论区给我留言。由于博客园有文件上传大小的限制所以我将Demo上传到了百度网盘中下载链接: https://pan.baidu.com/s/1jPHyXKV9DAK_oEYQz3xtzA 提取码: a7u5总结 以上就是关于Zipkin以及ASP.NET Core整合Zipkin的全部内容希望能给大家带来一定的帮助。如果你有实际需要也可以继续自行研究。Zipkin相对于我们常用的Skywalking而且它的使用方式比较原生许多操作都需要自行通过代码操作而SkyAPM可以做到对代码无入侵的方式集成。Skywalking是一款APM(应用性能管理)链路跟踪只是它功能的一部分。而Zipkin是一款专注于链路跟踪的系统个人感觉就链路跟踪这一块而言Zipkin更轻量级(如果都使用ES作为存储的数据库的话Skywalking会生成一堆索引Zipkin默认是每天创建一个索引)而且链路信息检索、详情展示、链路数据上报形式等相对于Skywalking形式也更丰富一些。但是整体而言Skywalking更强大比如应用监控、调用分析、集成方式等。技术并无好坏之分适合自己的才是更好的多一个解决方案就多一个解决问题的思路我觉得这是对于我们程序开发人员来说都应该具备的认知。????欢迎扫码关注我的公众号????