写点什么

.NET Core 3.0 的新改进:针对分布式应用程序的故障诊断和监控

  • 2019-11-15
  • 本文字数:5225 字

    阅读完需:约 17 分钟

.NET Core 3.0的新改进:针对分布式应用程序的故障诊断和监控

由于分布式应用是由多个组件组成的,且这些组件往往是由不同的团队拥有和操作,所以在与应用程序发生交互时,就会需要跨多个组件执行代码的分布式跟踪。如果用户遇到了问题,想要确定是哪个组件出现了差错,基本就是一件不可能完成的事情。


与单体应用程序相比,分布式应用程序的特点之一就是很难将单个分布式跟踪的遥测(如日志)关联起来。虽然用户可以通过查看日志了解到每个组件是如何处理每个请求的,但是很难知道一个组件中的请求和另一个组件中的请求是否属于同一分布式跟踪。


为了解决这个问题,应用性能管理厂商(APM)会提供从一个组件到另一个组件的分布式跟踪上下文传播功能。但是因为很多环境具有异构性、组件归属不同的团队,并通过不同的工具进行监视,因此对分布式应用程序进行一致的测试仍是难点。


随着 W3C Trace Contex 规范逐步过渡到 Proposed Recommendation maturity 级别,再加上其它供应商和平台对该规范的支持,上下文传播的复杂性正在降低。W3C Trace Contex 定义了分布式跟踪上下文的语义和格式,这使得分布式应用程序中的每个组件都可以理解上下文,并将其传播到调用的组件中。


为了使得分布式应用程序开发更容易,微软做了很多努力,例如 Orleans 框架和 Dapr 项目,而在分布式跟踪上下文传播,微软的服务和平台将采用 W3C Trace Contex 规范。同时,针对分布式跟踪和日志,.NET Core 3.0 做了很多新工作。

分布式跟踪和日志记录

.NET Core 3.0 在分布式跟踪方面的最新改进:


  • 两个开箱即用的.NET Core 3.0 应用程序在整个分布式跟踪中关联日志;

  • .NET Core 3.0 如何设置分布式跟踪上下文,如何自动跨 http 传播;

  • 同一个分布式跟踪标识如何被遥测 SDK 使用,例如 OpenTelemetry 和 ASP.NET Core logs。


为了帮助大家更深刻的理解.NET Core 3.0 的新改进,下面我们做了一个示例。

准备工作

该示例中,我们需要用到三个简单的组件:ClientApp、FrontEndApp 和 BackEndApp。BackEndApp 是一个名为 WeatherApp 的模板 ASP.NET Core 应用程序,可以通过公开的 REST API 来获取天气预报。而 FrontEndApp 会通过控制权将所有传入的请求发送给 BackEndApp。



[ApiController]
[Route("[controller]")]
public class WeatherForecastProxyController : ControllerBase
{
private readonly ILogger<WeatherForecastProxyController> _logger;
private readonly HttpClient _httpClient;
public WeatherForecastProxyController(
ILogger<WeatherForecastProxyController> logger,
HttpClient httpClient)
{
_logger = logger;
_httpClient = httpClient;
}
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
var jsonStream = await
_httpClient.GetStreamAsync("http://localhost:5001/weatherforecast");
var weatherForecast = await
JsonSerializer.DeserializeAsync<IEnumerable<WeatherForecast>>(jsonStream);
return weatherForecast;
}
}

复制代码


ClientApp 是一个 .NET Core 3.0 Windows Forms app,会调用 FrontEndApp 进行天气预报。


private async Task<string> GetWeatherForecast()
{
return await _httpClient.GetStringAsync(
"http://localhost:5000/weatherforecastproxy");
}
复制代码


需要注意的是,在这段示例中,没有安装任何额外的 SDK 和库。

查看日志

我们通过 ClientApp 调用来观察 FrontEndApp 和 BackEndApp 生成的日志。


FrontEndApp :


info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
=> ConnectionId:0HLR1BR0PL1CH
=> RequestPath:/weatherforecastproxy
RequestId:0HLR1BR0PL1CH:00000001,
SpanId:|363a800a-4cf070ad93fe3bd8.,
TraceId:363a800a-4cf070ad93fe3bd8,
ParentId:
Executed endpoint 'FrontEndApp.Controllers.WeatherForecastProxyController.Get (FrontEndApp)'
复制代码


BackEndApp:


info: BackEndApp.Controllers.WeatherForecastController[0]
=> ConnectionId:0HLR1BMQHFKRL
=> RequestPath:/weatherforecast
RequestId:0HLR1BMQHFKRL:00000002,
SpanId:|363a800a-4cf070ad93fe3bd8.94c1cdba_,
TraceId:363a800a-4cf070ad93fe3bd8,
ParentId:|363a800a-4cf070ad93fe3bd8.
Executed endpoint 'FrontEndApp.Controllers.WeatherForecastController.Get (BackEndApp)'
复制代码


与 magic 一样,来自两个独立应用程序的日志共享相同的 TraceId。ASP.NET Core 3.0 应用程序会初始化分布式跟踪上下文,并将其传递到头文件中。



FrontEndApp 并没有收到任何其它的头文件,出现这种情况的原因是在 ASP.NET Core 应用程序中,分布式跟踪是由 ASP.NET Core 框架自身在每次传入请求时启动的。


启动分布式跟踪

相信很多人都注意到了 Windows 窗体应用 ClientApp 和 ASP.NET Core FrontEndApp 在行为上的差异。ClientApp 未设置任何分布式跟踪上下文,因此 FrontEndApp 也不会收到。设置分布式操作最简单的方法是,使用 DiagnosticSource 包中名为 Activity 的 API。


private async Task<string> GetWeatherForecast()
{
var activity = new Activity("CallToBackend").Start();
try
{
return await _httpClient.GetStringAsync(
"http://localhost:5000/weatherforecastproxy");
}
finally
{
activity.Stop();
}
}
复制代码


启动之后,HttpClient 就知道需要传播分布式跟踪上下文。需要注意的是,ClientApp、FrontEndApp 和 BackEndApp 都共享同一个 TraceId。

W3C Trace Context

上下文使用 Request-Id 进行传播,这是从 ASP 中引入的,是默认生效的。但是,W3C Trace Context 正被广泛采用,因此,我们建议切换到 W3C Trace Context 的上下文传播格式。


在.NET Core 3.0 中,切换到 W3C Trace Context 格式只需要在主方法中添加一行代码:


static void Main()
{
Activity.DefaultIdFormat = ActivityIdFormat.W3C;

Application.Run(new MainForm());
}
复制代码


当 FrontEndApp 收到来自 ClientApp 的请求时,你会在请求中看到一个 traceparent 头文件:



通过这个头文件,.NET Core 应用程序就会明白现在需要通过 W3C Trace Context 格式来进行传播调用。需要注意的是,.NET Core 应用程序可以自动识别 W3C Trace Context 的正确格式,但是将分布式跟踪上下文的默认格式切换到 W3C Trace Context,可以在异构环境中实现更好的互操作性。


所有属性为 TraceId 和 SpanId 的日志:


info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
=> ConnectionId:0HLQV2BC3VP2T
=> RequestPath:/weatherforecast
RequestId:0HLQV2BC3VP2T:00000001,
SpanId:da13aa3c6fd9c146,
TraceId:f11a03e3f078414fa7c0a0ce568c8b5c,
ParentId:5076c17d0a604244
Request starting HTTP/1.1 GET http://localhost:5000/weatherforecast
复制代码

OpenTelemetry 的 Activity 和分布式跟踪

OpenTelemetry 提供了一组 API、库、代理和控制器服务,用于从应用程序捕获分布式跟踪和度量。


在调用时启动 AddOpenTelemetry,就可以在 BackEndApp 上启用 OpenTelemetry:


services.AddOpenTelemetry(b => 
b.UseZipkin(o => {
o.ServiceName="BackEndApp";
o.Endpoint=new Uri("http://zipkin /api/v2/spans");
})
.AddRequestCollector());
复制代码


FrontEndApp 日志中的 TraceId 与 BackEndApp 中的 TraceId 匹配。


info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
=> ConnectionId:0HLR2RC6BIIVO
=> RequestPath:/weatherforecastproxy
RequestId:0HLR2RC6BIIVO:00000001,
SpanId:54e2de7b9428e940,
TraceId:e1a9b61ec50c954d852f645262c7b31a,
ParentId:69dce1f155911a45
=> FrontEndApp.Controllers.WeatherForecastProxyController.Get (FrontEndApp)
Executed action FrontEndApp.Controllers.WeatherForecastProxyController.Get (FrontEndApp) in 3187.3112ms
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
=> ConnectionId:0HLR2RLEHSKBV
=> RequestPath:/weatherforecast
RequestId:0HLR2RLEHSKBV:00000001,
SpanId:0e783a0867544240,
TraceId:e1a9b61ec50c954d852f645262c7b31a,
ParentId:54e2de7b9428e940
=> BackEndApp.Controllers.WeatherForecastController.Get (BackEndApp)
Executed action BackEndApp.Controllers.WeatherForecastController.Get (BackEndApp) in 3085.9111ms
复制代码


此外,Zipkin 将报告相同的跟踪,因此,可以将分布式跟踪工具收集的分布式跟踪与来自计算机的日志关联起来。当 ClientApp 遇到问题时,可以将此 TraceId 提供给用户,由于用户和应用程序可以共享,因此能够更加容易的跨组件发现相应的日志和分布式跟踪。



另外,用户可以轻松启用对三个组件的监控,并在甘特图上查看:


ASP .NET Core 应用程序与分布式跟踪集成

APM 厂商收集到的遥测数据和 ASP .NET Core 使用的分布式跟踪上下文是相关的。因此,ASP .NET Core 3.0 应用程序非常适合不同团队拥有不同组件的场景。


例如,下图中的两个应用 A 和 C,启用了类似于 OpenTelemetry 的 SDK 遥测采集。如果不使用 ASP .NET Core 3.0,那么应用程序 B 就会“破坏”跟踪,导致分布式跟踪无法起作用。



在大多数部署中,ASP.NET Core 应用程序配置为启用基本日志记录,因此应用程序 B 将传播分布式跟踪上下文。而来自 A 和 C 的分布轨迹将相互关联。在以前的应用程序中,如果 ClientApp 和 BackEndApp 被感知,而 FrontEndApp 没有被感知,仍然可以看到分布式跟踪是相关的:



ASP.NET Core 应用程序非常适合服务网格环境。在服务网格部署中,上图中的 A 和 C 可以表示服务网格。为了让服务网格请求进入和离开组件 B,应用程序必须包含某些头文件。


虽然 Istio 能够自动发送 span,但是仍需要一些提示来连接整个跟踪。应用程序需要使用合适的 HTTP 头文件,以便在发送 span 消息时,能够正确关联到单个跟踪中。如果是采用 W3C Trace Context 格式,ASP.NET Core 应用程序则无需做任何改变。

传递附加上下文

如果你希望能够在分布式应用程序的组件之间共享更多上下文,可以添加以下属性:


private async Task<string> GetWeatherForecast()
{
var activity = new Activity("CallToBackend")
.AddBaggage("appVersion", "v1.0")
.Start();
try
{
return await _httpClient.GetStringAsync(
"http://localhost:5000/weatherforecastproxy");
}
finally
{
activity.Stop();
}
}

复制代码


服务器端,在 FrontEndApp 和 BackEndApp 可以看到一个额外的头文件 Correlation-Context。



使用 Activity.Baggage:


var appVersion =  Activity.Current.Baggage.FirstOrDefault(b => b.Key == "appVersion").Value;
using (_logger.BeginScope($"appVersion={appVersion}"))
{
_logger.LogInformation("this weather forecast is from random source");
}

复制代码


作用域中包含 appVersion:


info: FrontEndApp.Controllers.WeatherForecastController[0]
=> ConnectionId:0HLQV353507UG
=> RequestPath:/weatherforecast
RequestId:0HLQV353507UG:00000001,
SpanId:37a0f7ebf3ecac42,
TraceId:c7e07b7719a7a3489617663753f985e4,
ParentId:f5df77ba38504846
=> FrontEndApp.Controllers.WeatherForecastController.Get (BackEndApp)
=> appVersion=v1.0
this weather forecast is from random source
复制代码

未来发展

随着 ASP.NET Core 3.0 的改进,很多 ASP .NET Core 包含的功能可能就难以使用了,比如开发人员和 DevOps 想要做一个交钥匙遥测解决方案,需要和很多 APM 的厂商来做。但是,我们在 OpenTelemetry 方面会加大投入,使得更多 ASP .NET Core 用户能够在监控和故障排除方面变得更容易。


我们会帮助用户采用 W3C Trace Context,并且在 ASP .NET Core 的未来版本中可能将其作为默认的分布式跟踪上下文传播格式。


另外,我们还会专注于改进分布式上下文传播场景。与 Monolits 相比,分布式应用程序在单个分布式跟踪的生存期缺少公共共享状态,而这种共享状态可以用于基本的日志记录、用于请求的高级路由、实验、A/B 测试、业务上下文传播等。


原文链接:


https://devblogs.microsoft.com/aspnet/improvements-in-net-core-3-0-for-troubleshooting-and-monitoring-distributed-apps/


2019-11-15 08:532787

评论

发布
暂无评论
发现更多内容

9月24日,线下见个面?

MatrixOrigin

数据库 分布式 MatrixOrigin MatrixOne

Databend 开源周报第 110 期

Databend

使用消息队列有什么优点?有什么缺点?

程序员在修行

ARTS 打卡第四周

程序员在修行

不知道该选公有云还是私有云?这些客户请选私有云

青椒云云电脑

桌面云 云桌面

用AI提高代码质量,同事对我的代码赞不绝口~

飞算JavaAI开发助手

Footprint Analytics 为 ABGA Web3 Gaming Summit 提供支持,助力 Web3 游戏行业发展

Footprint Analytics

数字化转型与架构-架构设计篇|如何开发一个各部门都满意的系统?

数字随行

数字化转型

数据通信网络之IPv6以太网单臂路由

timerring

数据通信网络

INFINI Easysearch 与兆芯完成产品兼容互认证

极限实验室

easysearch 兆芯 国产适配

序列化与反序列化一棵树

程序员在修行

如何赋能企业数字化转型?华为云有妙招

YG科技

与中小企业共生共赢,华为云B2B企业节来了

YG科技

Ask Milvus Anything!聊聊被社区反复@的那些事儿 Ⅰ

Zilliz

非结构化数据 Milvus 向量数据库 deepdive

Last Week in Milvus

Zilliz

非结构化数据 Milvus Zilliz 向量数据库

云迁移?是技术问题也是团队问题

在天涯的海角

从构建者到设计者的低代码之路

树上有只程序猿

软件开发 低代码

议题征集|Flink Forward Asia 2023 正式启动

Apache Flink

flink

亚信科技与中国信通院达成全方位、跨领域战略合作

亚信AntDB数据库

AntDB 国产数据库 AntDB数据库

Mybatis中传递多个参数的4种方法

源字节1号

开源 软件开发 前端开发 后端开发 小程序开发

从实时监控到智能洞察:Grafana 和 CnosDB 的无限潜力

CnosDB

时序数据库 开源社区 CnosDB

828 B2B企业节,华为云如何提升生态合作伙伴的综合竞争力?

YG科技

高校云桌面的“正确打开方式”是什么?

青椒云云电脑

云桌面 云桌面方案

【转载】JVM实际内存占用超过Xmx的原因,设置Xmx的技巧

虚实的星空

Java

英文阅读 The manager's path 1.3

程序员在修行

百度智能云千帆社区上线有礼,助力开发者开启大模型之路!

科技热闻

一文读懂私有云、公有云和本地化部署

青椒云云电脑

公有云 私有云

关于身心灵学习的感悟1

程序员在修行

.NET Core 3.0的新改进:针对分布式应用程序的故障诊断和监控_语言 & 开发_Sourabh Shirhatti_InfoQ精选文章