ASP.NET Core整合Zipkin链路跟踪

前言

    在日常使用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.type=single-node- bootstrap.memory_lock=true#es有内存要求- "ES_JAVA_OPTS=-Xms512m -Xmx512m"ulimits:memlock:soft: -1hard: -1networks:default:aliases:- elasticsearchzipkin:image: openzipkin/zipkincontainer_name: zipkinrestart: alwaysnetworks:default:aliases:- zipkinenvironment:#存储类型为es- STORAGE_TYPE=elasticsearch#es地址- ES_HOSTS=elasticsearch: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 Core

ZipKin启动成功之后,我们就可以将程序中的数据采集到Zipkin中去了,我新建了两个ASP.NET Core的程序,一个是OrderApi,另一个是ProductApi方便能体现出调用链路,其中OrderApi调用ProductApi接口,在两个项目中分别引入Zipkin依赖包

<PackageReference Include="zipkin4net" Version="1.5.0" />
<PackageReference Include="zipkin4net.middleware.aspnetcore" Version="1.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.AddScoped<NacosDiscoveryDelegatingHandler>();services.AddHttpClient(ServiceName.ProductService,client=> {client.BaseAddress = new Uri($"http://{ServiceName.ProductService}");}).AddHttpMessageHandler<NacosDiscoveryDelegatingHandler>()//引入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 List<OrderDto> orderDtos = new List<OrderDto>();private readonly IHttpClientFactory _clientFactory;public OrderController(IHttpClientFactory clientFactory){orderDtos.Add(new OrderDto { Id = 1, TotalMoney=222,Address="北京市",Addressee="me",From="淘宝",SendAddress="武汉" });_clientFactory = clientFactory;}/// <summary>/// 获取订单详情接口/// </summary>/// <param name="id">订单id</param>/// <returns></returns>[HttpGet("getdetails/{id}")]public async Task<OrderDto> 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.DeserializeObject<List<OrderProductDto>>(result);return orderDetailDto;}return orderDto;}
}

在ProductApi中我们只需要编写调用RegisterZipkinTrace方法即可,和OrderApi一样,我们就不重复粘贴了。因为ProductApi不需要调用别的服务,所以可以不必使用集成HttpClient,只需要提供简单的接口即可

[Route("productapi/[controller]")]
public class ProductController : ControllerBase
{private List<ProductDto> productDtos = new List<ProductDto>();public ProductController(){productDtos.Add(new ProductDto { Id = 1,Name="酒精",Price=22.5m });productDtos.Add(new ProductDto { Id = 2, Name = "84消毒液", Price = 19.9m });}/// <summary>/// 获取所有商品信息/// </summary>/// <returns></returns>[HttpGet("getall")]public IEnumerable<ProductDto> 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 Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{Func<HttpRequestMessage, string> _getClientTraceRpc = _getClientTraceRpc = getClientTraceRpc ?? (request => request.Method.ToString());IInjector<HttpHeaders> _injector = Propagations.B3String.Injector<HttpHeaders>((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 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("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 :IObserver<DiagnosticListener>
{private IEnumerable<ITraceDiagnosticListener> _traceDiagnostics;public TraceObserver(IEnumerable<ITraceDiagnosticListener> traceDiagnostics)
{_traceDiagnostics = traceDiagnostics;}public void OnCompleted()
{}public void OnError(Exception error)
{}public void OnNext(DiagnosticListener listener)
{//这样的话我们可以更轻松的扩展其他DiagnosticListener的操作var traceDiagnostic = _traceDiagnostics.FirstOrDefault(i=>i.DiagnosticName==listener.Name);if (traceDiagnostic!=null){//适配订阅listener.SubscribeWithAdapter(traceDiagnostic);}}
}

通过这种操作我们就无需关心如何将自定义的DiagnosticListener订阅类适配到DiagnosticAdapter中去,方便我们自定义其他DiagnosticListener的订阅类,这样的话我们只需注册自定义的订阅类即可。

services.AddSingleton<TraceObserver>();
services.AddSingleton<ITraceDiagnosticListener, 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更强大,比如应用监控、调用分析、集成方式等。技术并无好坏之分,适合自己的才是更好的,多一个解决方案,就多一个解决问题的思路,我觉得这是对于我们程序开发人员来说都应该具备的认知。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307786.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探秘react,一文弄懂react的基本使用和高级特性

一文详解react的基本使用、高级特性和周边插件⏰序言&#x1f4dd;一、React的基本使用1、JSX基本使用&#xff08;1&#xff09;变量、表达式&#xff08;2&#xff09;class和style&#xff08;3&#xff09;子元素和组件&#xff08;4&#xff09;原生 html2、条件判断&…

使用React hooks,些许又多了不少摸鱼时间

一文详解react-hooks&#x1f399;️前言一、&#x1f4fb;概述1、关于React Hooks2、认识React Hooks&#xff08;1&#xff09;回顾React函数式组件&#xff08;2&#xff09;函数组件的特点&#xff08;3&#xff09;class组件的问题&#xff08;4&#xff09;React 组件二、…

手把手教学之如何设计财务对账系统

在设计一个对账系统前&#xff0c;我们先想清楚&#xff0c;对账目的、方式、对账场景、使用对象。其次就是和财务了解一下&#xff0c;他们所期望的效果&#xff0c;毕竟他们是使用方。财务对账是一个琐碎而复杂的工作&#xff0c;极需要工作耐心和细心&#xff0c;还要求对账…

不平凡的2021,末流普本生秋招上岸大厂的历程

&#x1f4fb;叮&#xff01; 2021年接近尾声&#xff0c;周一也开始拾起了年终总结。 回顾2021&#xff0c;有喧嚣&#xff0c;也有欢呼&#xff1b;有奔溃&#xff0c;也有快乐。 但好在✏️ 对待一件又一件的小事上&#xff0c;始终保持着对自己的要求&#x1f4ca; 不…

leetcode491. 递增子序列

一&#xff1a;论语 二:题目 三&#xff1a;上码 class Solution { public:vector<vector<int> >ans;vector<vector<int> >ans1;vector<int> path;void backstacking(vector<int>& nums,int index) {if(path.size() > 2) {ans.pus…

送福利 | 送书5本《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发

《ASP.NET Core项目开发实战入门》从基础到实际项目开发部署带你走进ASP.NET Core开发。ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 所写&#xff0c;后续ASP.NET Core 5也会对应更新。简介本书共14章&#xff0c;深入浅出地介绍了ASP.NET Core基础及项目开发方面的知…

海有舟可渡、山有路可循‍‍ —— 大学四年圆满落幕

⛺️前言 仅以此篇文章记录我的大学四年&#xff01; 回忆大学四年&#xff0c;有惊喜、有快乐、有崩溃、有欢呼&#xff1b; 有过无数个为了赶策划、赶ddl的熬夜&#xff0c;更有凌晨2点做志愿活动、凌晨5点赶项目的通宵达旦。 梳理下大学四年的大事件&#xff1a; 大一 …

程序开发天团必备单品,稳定输出还加持千元商务礼限时送!

有这么一个门派他们用强大的思维逻辑频出“大招”用抗造的硬核体能昼夜练习一水儿的格子衫下&#xff0c;个个骨骼精奇深居简出&#xff0c;却默默改变着世界格局他们都使用同一件武器拼的就是个配置高低这与能否称霸武林有着直接关系看看各位高手怎么说“大内”高手 武功唯快不…

了解微前端,深入前端架构的前世今生

前端架构的前世今生&#x1f6f5;前言一、&#x1f6f4;前端架构的前世今生1、架构是如何产生的&#xff1f;2、MVC架构3、前后端分离架构4、Nodejs5、单页面架构&#xff08;1&#xff09;现有单页面架构&#xff08;2&#xff09;单页面架构的优势&#xff08;3&#xff09;单…

leetcode37. 解数独

一:论语 简而言之 就是要一视同仁 对待遇见所有的人要一个态度 二&#xff1a;题目 三:上码 class Solution { public:bool backstacking(vector<vector<char> >& board) {for(int i 0; i < board.size(); i) {for(int j 0; j < board[0].size(); …

跟我一起学.NetCore之.NetCore概述

随着.NetCore版本发布变更&#xff0c;在最近一年左右开始接触到.NetCore&#xff0c;之前只是传闻&#xff0c;并没有动手实操&#xff1b;.NetCore逐渐成熟&#xff0c;大大小小的公司也开始进行使用&#xff0c;感觉再不学习就落后了&#xff0c;于是乎搜索各种资料开始学习…

线程与线程池(一条龙详解)

一:前言 一个问题引出的学习笔记 并发类库提供的线程池实现有哪些? 其实Executors已经为我们封装好了 4 种常见的功能线程池&#xff0c;如下&#xff1a; 定长线程池&#xff08;FixedThreadPool&#xff09;定时线程池&#xff08;ScheduledThreadPool &#xff09;可缓存…

项目升级,无缝对接 .NET 5

开启.NET5时代2020-09-14从NetCore1.1开始学起&#xff0c;然后又从2.0开始讲知识&#xff0c;再到将所有的在线项目升级并长期维护到3.1&#xff0c;转眼已经三年了&#xff0c;一直紧跟着微软的节奏有条不紊的往前走&#xff0c;我相信&#xff0c;只要是从18年末或者19年初跟…

小团队前端部署演化之路

前言 前端部署相对来说其实是一件非常容易的事情&#xff0c;无论是最原始的html页面&#xff0c;还是现在热门的三大框架&#xff0c;最后交付部署的时候&#xff0c;始终会是一些静态文件。虽然简单&#xff0c;但是对于不同的团队来说&#xff0c;都会在不同阶段有最适合他们…

GitHub 全域数字年报:携手推动开源世界的超级协作

2020年1月24日&#xff0c;Wuhan2020开源项目正式发起&#xff0c;在疫情期间累积吸引到了约3000余位技术志愿者以及近1000余位非技术志愿者在线上开展志愿行动与参与。Wuhan2020在成立后的约3个月时间内&#xff0c;通过开源协作的方式在互联网上开展志愿者支持与工作协同&…

leetcode53. 最大子数组和(暴力+贪心)

一:论语 追求利益的同时 我们需要控制度 就好比鹅厂的王者荣耀 赚的盆满钵满 坑坏了多少青少年 但是鹅厂早已经开始控制度了 二:题目 三:上码&#xff08;暴力贪心&#xff09; 1:暴力 class Solution { public:int maxSubArray(vector<int>& nums) {/**暴力解法…

Newbe.Claptrap-一套以“事件溯源”和“Actor模式”作为基本理论的服务端开发框架...

本文是关于 Newbe.Claptrap 项目主体内容的介绍&#xff0c;读者可以通过这篇文章&#xff0c;大体了解项目内容。轮子源于需求随着互联网应用的蓬勃发展&#xff0c;相关的技术理论和实现手段也在被不断创造出来。诸如 “云原生架构”、“微服务架构”、“DevOps” 等一系列关…

NCF框架揭秘直播来了!红包、抽奖、还有神秘嘉宾…(内含彩蛋)

盛派周三技术分享会直播开讲又来啦~为了更好地赋能开发者&#xff0c;盛派已将系统框架 SCF&#xff08;SenparcCoreFramework&#xff09;全部开源&#xff0c;收到了社区非常多的关注&#xff0c;现在 SCF 已正式更名为NCF&#xff08;NeuCharFramework&#xff09;&#xff…

leetcode45. 跳跃游戏 II

一:论语 己所欲 也要勿施于人 &#xff0c;每个人的经历和阅历都是不同的 你凭啥说你认为的很开心的事情 去要求别人呢 二:题目 三:上码 class Solution { public:int jump(vector<int>& nums) {/**思路:1.这里的难点就在于 我们需要判断下一步的的最远跳跃距离…

leetcode1005. K 次取反后最大化的数组和

一:论语 这个用在自己身上感觉值得反省&#xff0c;很多道理我都能明白 也能讲给别人听 但是很多时候 自己往往做的不好 而且还很容易 自我感动 最近真的很讨厌自己这样 不要自我感动 要正向积累 多去做 多去做 这只是个开始 然后慢慢的长进 再者就是坚持 二:题目 三:上码 …