链路追踪在ERP系统中的应用实践

源宝导读:随着ERP的部署架构越来越复杂,对运维监控、问题排查等工作增加了难度,本文将介绍通过引入链路追踪技术,提高ERP系统问题排查效率,支撑更全面监控系统运行情况的实践过程。

一、导读

    随着ERP的部署架构越来越复杂,微应用分布式部署架构在给用户带来高性能高稳定的同时,给运维监控、问题排查带来了一定难度,特别是后端服务的内部调用由于缺少日志,难以快速定位问题根因。借助链路追踪并结合异常日志所记录的链路id,可以方便定位整个异常链路,更快找到问题的原因。链路追踪还有一个好处,针对性能慢的页面,原来很难快速定位到慢的具体点,通过借助链路追踪,能快速掌握每个请求执行了哪些操作,每个操作消耗了多长时间,精准定位性能问题。

二、链路追踪介绍

    分布式系统变得日趋复杂,越来越多的组件开始走向分布式化,如微服务、分布式数据库、分布式缓存等,使得后台服务构成了一种复杂的分布式网络。

    在服务能力提升的同时,复杂的网络结构也使问题定位更加困难。在一个请求在经过诸多服务过程中,出现了某一个调用失败的情况,查询具体的异常由哪一个服务引起的就变得十分抓狂,问题定位和处理效率是也会非常低。

    分布式链路追踪就是将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。

    目前业界的链路追踪系统,如Twitter的Zipkin,Uber的Jaeger,国内比较流行的SkyWalking。

三、ERP中链路追踪的实现

什么是 Diagnostics

    在.NET Core中实现链路追踪非常简单,因为在 .NET Core 中 .NET 团队设计了一个全新的 DiagnosticSource,新的 DiagnosticSource 非常的简单,它允许你在生产环境记录丰富的 payload 数据,然后你可以在另外一个消费者中消费感兴趣的记录。

    ERP因为是.NET Framework实现链路追踪复杂一些,对此为了实现链路追踪,我们将Diagnostics应用到ERP中,手动创建生产者,然后在具体的链路追踪中创建对应消费者采集数据。

    Diagnostics 就是提供一组功能使我们能够很方便的可以记录在应用程序运行期间发生的关键性操作以及他们的执行时间等,使管理员可以查找特别是生产环境中出现问题所在的根本原因。

    在应用程序出现问题的时候,特别是出现可用性或者性能问题的时候,开发人员或者IT人员经常会对这些问题花费大量的时间来进行诊断,很多时候生产环境的问题都无法复现,这可能会对业务造成很大的影响。

    目前平台实现了两种对接SkyWalking和Jaeger,可以方便的在配置中心启用,接入自建服务或云服务都是支持。

SkyWalking对接

    SkyWalking目前对于.NET Core有着很好的支持,但是对于.NET Framework因为本身Diagnostics的原因,只有一个很简单的ASP.NET请求客户端。

    其它的客户端我们都做了一些改造,但有一些我们也尽量标准,如SqlClient、HttpClient。以下介绍一下Redis的支持,完全自己实现客户端。

    首先要创建生产者DiagnosticListener,然后在Redis的相关操作里执行具体事件。

    DiagnosticListener需要引入System.Diagnostics.DiagnosticSource这个dll,以下为简单实现执行前事件:

internal static class RedisDiagnosticListenerExtensions
{/// <summary>/// 定义监听的名称/// </summary>public const string DiagnosticListenerName = "RedisDataDiagnosticListener";/// <summary>/// 前缀/// </summary>public const string CacheDataPrefix = "Mysoft.Map6.Cache.";/// <summary>/// 执行前事件名称/// </summary>public const string CacheBeforeExecuteName = CacheDataPrefix + nameof(RedisExecuteBefore);public static Guid RedisExecuteBefore(this DiagnosticListener @this, string operation,string endpoint, string cacheKey, long? cacheLength = null){if (@this.IsEnabled(CacheBeforeExecuteName)){Guid operationId = Guid.NewGuid();@this.Write(CacheBeforeExecuteName,new{OperationId = operationId,Operation = operation,Endpoint = endpoint,CacheKey = cacheKey,CacheLength = cacheLength,Timestamp = Stopwatch.GetTimestamp()});return operationId;}elsereturn Guid.Empty;}
}

    然后在具体行为里面执行事件,以下写入Redis缓存为例:

private bool WriteToRedis(string key, byte[] value, TimeSpan expires)
{var operationId = s_diagnosticListener.RedisExecuteBefore(nameof(WriteToRedis), _redisProvider.GetCurrentEndPoint().ToString(), key, cacheLength: value?.Length);try{var result = RetryExecute(() => _redisProvider.GetRedis().GetDatabase().StringSet(key, value, expires));s_diagnosticListener.RedisExecuteAfter(operationId, nameof(WriteToRedis), _redisProvider.GetCurrentEndPoint().ToString(),  key);return result;}catch (Exception ex){s_diagnosticListener.RedisExecuteError(operationId, ex, endpoint: _redisProvider.GetCurrentEndPoint().ToString(), cacheKey: key, cacheLength: value?.Length);throw;}
}

    在写入Redis缓存前执行RedisExecuteBefore传入基本信息,然后写入Redis缓存完成再执行RedisExecuteAfter,如果写入Redis缓存发生异常就会执行RedisExecuteError。

    最终在消费实现的时候,根据对应的信息构造链路跟踪信息,在After和Error中写入链路信息到队列。具体逻辑如下:

internal class RedisDiagnosticProcessor : ITracingDiagnosticProcessor{public static string ComponentName = "StackExchange.Redis";private readonly ITracingContext _tracingContext;private readonly IExitSegmentContextAccessor _contextAccessor;public string ListenerName => RedisDiagnosticStrings.DiagnosticListenerName;public RedisDiagnosticProcessor(ITracingContext tracingContext,IExitSegmentContextAccessor contextAccessor){_tracingContext = tracingContext;_contextAccessor = contextAccessor;}private static string ResolveOperationName(string operation){return $"{RedisDiagnosticStrings.CacheDataPrefix}  {operation}";}[DiagnosticName(RedisDiagnosticStrings.CacheBeforeExecuteName)]public void CacheExecuteBefore([Property(Name = "Endpoint")] string endpoint,[Property(Name = "CacheKey")] string cacheKey, [Property(Name = "CacheLength")]long? cacheLength, [Property(Name = "Operation")]string operation){var context = _tracingContext.CreateExitSegmentContext(ResolveOperationName(operation), endpoint);context.Span.SpanLayer = SpanLayer.CACHE;context.Span.Component = new StringOrIntValue(ComponentName);context.Span.AddTag(CacheTags.CACHEKEY, cacheKey);if (cacheLength != null){context.Span.AddTag(CacheTags.CACHELENGTH, cacheLength.Value);}context.Span.AddTag(CacheTags.OPERATION, operation);}[DiagnosticName(RedisDiagnosticStrings.CacheAfterExecuteName)]public void CacheExecuteAfter([Property(Name = "CacheLength")]long? cacheLength){var context = _contextAccessor.Context;if (context != null){if (cacheLength != null){context.Span.AddTag(CacheTags.CACHELENGTH, cacheLength.Value);}_tracingContext.Release(context);}}[DiagnosticName(RedisDiagnosticStrings.CacheErrorExecuteName)]public void CacheExecuteError([Property(Name = "Exception")] Exception ex){var context = _contextAccessor.Context;if (context != null){context.Span.ErrorOccurred(ex);_tracingContext.Release(context);}}}

  ListenerName 就是上面RedisDiagnosticListener定义的名称。因为CacheExecuteBefore、CacheExecuteAfter这些方法都是通用的,同样会应用于读取Redis缓存和移除Redis缓存。

    SkyWalking会构造一个 SegmentContext,在CacheExecuteBefore的时候构造Context信息,然后CacheExecuteAfter或CacheExecuteError发布Context 信息。

    同时在SkyWalking的.NET客户端里会维护一个队列,将链路Context信息缓存在其中,例如当满足一定条件:如达到1000条或5秒钟等条件,就会推送至SkyWalking服务端。这个在客户端初始化的时候是可以配置的。

Jaeger对接

   Jaeger的对接,在客户端上稍微做了一些改动。由于其中一个库OpenTracing.Contrib没有Framework版,平台单独编译的一个Framework版本。

    然后Jaeger客户端是完美支持OpenTracing标准,平台采集数据的是标准OpenTracing格式,对接其它客户端也是会非常容易。

    OpenTracing 是与后台无关的一套接口,被跟踪的服务只需要调用这套接口,就可以被任何实现这套接口的跟踪后台(比如Zipkin, Jaeger等等)支持,而作为一个跟踪后台,只要实现了个这套接口,就可以跟踪到任何调用这套接口的服务。

  • 标准化了对跟踪最小单位Span的管理:定义了开始Span,结束Span和记录Span耗时的API。Span的定义可以参照开源分布式跟踪系统Zipkin介绍(架构篇)

  • 标准化了进程间跟踪数据传递的方式:定义了一套API方便跟踪数据的传递

  • 标准化了进程内当前Span的管理:定义了存储和获取当前Span的API

    以下是Redis对应监听的实现:

internal class RedisDiagnosticObserver : DiagnosticListenerObserver
{public static string ComponentName = "StackExchange.Redis";private readonly ITracer _tracer;/// <inheritdoc />public RedisDiagnosticObserver(ILoggerFactory loggerFactory, ITracer tracer,IOptions<GenericEventOptions> genericEventOptions) : base(loggerFactory, tracer, genericEventOptions.Value){_tracer = tracer;}/// <inheritdoc />protected override string GetListenerName() => RedisDiagnosticStrings.DiagnosticListenerName;/// <inheritdoc />protected override void OnNext(string eventName, object untypedArg){switch (eventName){case RedisDiagnosticStrings.CacheBeforeExecuteName:RedisExecuteBefore(untypedArg);break;case RedisDiagnosticStrings.CacheAfterExecuteName:RedisExecuteAfter(untypedArg);break;case RedisDiagnosticStrings.CacheErrorExecuteName:RedisExecuteError(untypedArg);break;}}
}

    以上对应的具体方法省略,跟SkyWalking类似,其中主要构建的是Scope,然后内部是Span,这些内容都是OpenTracing中的对象,最终再引入Jaeger的客户端即可完成接入。

    ERP整个链路数据流转如下图:

四、与ERP结合

    由于ERP基于.NET Framework,有很多场景是需要自己调整。具体在ERP中如何接入调整的,下面可以看看,目前初始化使用的动态HttpModule注入,然后在HttpModule中进行初始化。

    动态注入,利用System.Web.PreApplicationStartMethod特性在程序启动时执行方法,然后使用DynamicModuleUtility中RegisterModule方法注册HttpModule。要使用RegisterModule方法,需要引入Microsoft.Web.Infrastructure类库。

[assembly: System.Web.PreApplicationStartMethod(typeof(InstrumentModuleFactory), nameof(InstrumentModuleFactory.Create))]
namespace Mysoft.Map6.OpenTracing.Startup
{public class InstrumentModuleFactory{public static void Create(){DynamicModuleUtility.RegisterModule(typeof(InstrumentModule));}}
}

    然后HttpModule 的初始化,整体流程如下:

    首先HttpModule的Init方法会在初始化的时候执行,但是在ASP.NET的请求中,会多次初始HttpModule,这样会导致链路追踪的方法多次初始化,同时内部的ServiceProvider多次build这是不合理。

    所以在此基础上做了调整,使用双重锁,确保初始化只执行一次,同时在开启链路追踪的时候才初始化。

    链路追踪目前的设计,只支持开启其中的一种,这个可以在配置中心进行配置。

    在HttpModule初始化的时候,绑定BeginRequest和EndRequest事件,对应的实现是追踪ASP.NET的请求数据,在EndRequest中往链路追踪写入数据。这样对于ERP整个请求可以实现完整的链路。

五、应用效果

   最终接入链路追踪的效果,以SkyWaking为例,SkyWaking UI比较全,可以看到整个服务以及对应链路的详细信息。

服务信息:

链路信息:

    以上可以看到整个ERP系统服务的拓补图。ERP在开启链路追踪以后,每个请求都会有对应TraceId,利用TraceId可以到链路详细信息中查询对应信息、时间等数据,对于后续性能分析及异常分析都提供良好的条件。

    ERP接入链路追踪以后可以方便定位请求性能,同时对应异常、性能日志中也会记录TraceId,为排查问题提供方便支持。

------ END ------

作者简介

张同学: 研发工程师,目前在ERP建模平台团队负责开发工作。

也许您还想看

【复杂系统迁移 .NET Core平台系列】之静态文件

【复杂系统迁移 .NET Core平台系列】之迁移项目工程

【复杂系统迁移 .NET Core平台系列】之界面层

.NET Core MVC扩展实践

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

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

相关文章

[Java基础]File基础

File类概述和构造方法: 代码如下: package FileStudyPack;import java.io.File;public class FileDemo01 {public static void main(String[] args){File f1 new File("D:\\JavaDemo\\java.txt");System.out.println(f1);File f2 new File("D:\\JavaDemo&quo…

java 日志乱码_【开发者成长】JAVA 线上故障排查完整套路!

云栖号资讯&#xff1a;【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯&#xff0c;还在等什么&#xff0c;快来&#xff01;线上故障主要会包括 CPU、磁盘、内存以及网络问题&#xff0c;而大多数故障可能会包含不止一个层面的问题&#xff0c;所以进行…

谈谈登录密码传输这件小事

背景 大大小小的系统其实都离不开登录这个小小的功能&#xff0c;前段时间老黄在审查公司部分系统代码时&#xff0c;发现不少系统的登录还是很粗暴的&#xff0c;粗暴到让人不敢说话的那种。说到登录&#xff0c;结合标题&#xff0c;其实大部分人应该都猜到那个粗暴到让人不敢…

技术分享杂七杂八技术

技术分享 听花谷 距离名宿 6~7 公里左右&#xff0c;丽江网红基地&#xff0c;有举办婚礼的地方听花谷&#xff0c;坐落于玉龙雪山脚下&#xff0c;前有玉龙雪山&#xff0c;后有原始森林。园内共有三处白色空间&#xff0c;第一处共有三层&#xff0c;婚礼举行&#xff0c;发…

java 操作日志设计_日志系统新贵 Loki,确实比笨重的ELK轻

本文同步Java知音社区&#xff0c;专注于Java作者&#xff1a;linkt1234http://blog.csdn.net/Linkthaha/article/details/100575278最近&#xff0c;在对公司容器云的日志方案进行设计的时候&#xff0c;发现主流的ELK或者EFK比较重&#xff0c;再加上现阶段对于ES复杂的搜索功…

Istio1.5 Envoy 数据面 WASM 实践

Istio 1.5 回归单体架构&#xff0c;并抛却原有的 out-of-process 的数据面扩展方式&#xff0c;转而拥抱基于 WASM 的 in-proxy 扩展&#xff0c;以期获得更好的性能。本文基于网易杭州研究院轻舟云原生团队的调研与探索&#xff0c;介绍 WASM 的社区发展与实践。超简单版解释…

elasticSearch -- (文档,类型,索引)

问题:大规模数据如何检索 当系统数据量达到10亿&#xff0c;100亿级别的时候&#xff0c;我们系统该如何去解决这种问题。 数据库选择—mysql&#xff0c; sybase&#xff0c;oracle&#xff0c;mongodb&#xff0c;hbase…单点故障如何解决—lvs&#xff0c; F5&#xff0c;…

asp后台调用产品数据_后台产品经理,需掌握这些数据交互知识

人们每天都在接收信息和发送信息&#xff0c;在传递信息的过程中&#xff0c;明白对方要表达的意思。数据也是如此&#xff0c;在系统交换数据的过程中&#xff0c;就伴随着数据交互。本篇文章将为大家具体分析前端和后台的数据交互与协议。本文所说的”数据交换” 是指在计算机…

使用c# .net core开发国标gb28181 sip +流媒体服务完成视频监控实例教程 亲身完美体验过程...

目前使用C# .net core 来实现国标gb28181标准的摄像头播放、ptz云台控制、视频回放等视频监控功能&#xff0c;项目可运行于linux/docker/.net core环境&#xff0c;也是当前非常罕有的能做到毫秒级国标gb28181公网视频传送案例&#xff0c;也是少有的能同时具有播放、ptz云台控…

[Java基础]字节流读数据

代码如下: package InstreamPack;import java.io.FileInputStream; import java.io.IOException;public class FileInputStreamDemo01 {public static void main(String[] args) throws IOException {FileInputStream fis new FileInputStream("D:\\JavaDemo\\java.txt&…

丁可以组什么词_有哪些量词可以用来描述生意经?

分别有&#xff1a;本&#xff0c; 一(本)生意经。笔&#xff0c; 一(笔)生意经。次&#xff0c;一(次)生意量词用名量词&#xff1a;表示事物的计量单位。基本定义&#xff1a;通常用来表示人、事物或动作的数量单位的词&#xff0c;叫做量词。量词 lingc&#xff0c;与代表可…

硬核技能k8s初体验

&#xff0c;Kubernetes 是一个软件系统&#xff0c;使你在数以万计的电脑节点上运行软件时就像所有节点是以单个大节点一样&#xff0c; 它将底层基础设施抽象&#xff0c;这样做同时简化了应用开发、部署&#xff0c;以及对开发和运维团队的管理。Kubernetes集群架构Kubernet…

线上问题排查流程

问题排查 针对各种常见的线上问题&#xff0c;梳理下排查思路。 业务问题 线上问题大多数时候都是业务问题引发的问题&#xff0c;当线上环境绝大多数请求都是正常&#xff0c;当有部分或者某一个用户有问题&#xff0c;此时怎么针对性的排查在当前微服务体系下&#xff0c;…

springboot超详细教程_CG原画插画教程:超详细线稿教程

艺学绘小编收集整理了超详细线稿教程今天我们从起稿开始1. 用圆表示出人物的头部、胸腔和骨盆&#xff0c;分别画出头部的十字线和身体的中线&#xff0c;表示头和身体的朝向。用“火柴棍”表示四肢的动作形态。▼2. 根据圆和十字线画出脸的轮廓&#xff0c;再在身体骨架上画出…

Asp.Net.Core WebApi 版本控制

前言在后端Api的开发过程中,无法避免的会遇到接口迭代的过程,如何保证新老接口的共存和接口的向前的兼容呢,这时候就需要对Api进行版本的控制,那如何优雅的控制Api的版本呢&#xff1f;开始Microsoft.AspNetCore.Mvc.Versioning 是一个微软官方推出的一个用于管理Api版本的包,配…

[Java基础]字节缓冲流

字节缓冲输出流: package InstreamPack;import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets;public class BufferStreamDemo01 {public s…

用asp.net core结合fastdfs打造分布式文件存储系统

最近被安排开发文件存储微服务&#xff0c;要求是能够通过配置来无缝切换我们公司内部研发的文件存储系统&#xff0c;FastDFS&#xff0c;MongDb GridFS&#xff0c;阿里云OSS&#xff0c;腾讯云OSS等。根据任务紧急度暂时先完成了通过配置来无缝切换内部文件系统以及FastDFS。…