淄博怎么做网站,淄博公司制作网站有哪些,wordpress表单 post,网络舆情监测是什么工作源宝导读#xff1a;随着ERP的部署架构越来越复杂#xff0c;对运维监控、问题排查等工作增加了难度#xff0c;本文将介绍通过引入链路追踪技术#xff0c;提高ERP系统问题排查效率#xff0c;支撑更全面监控系统运行情况的实践过程。一、导读随着ERP的部署架构越来越复杂… 源宝导读随着ERP的部署架构越来越复杂对运维监控、问题排查等工作增加了难度本文将介绍通过引入链路追踪技术提高ERP系统问题排查效率支撑更全面监控系统运行情况的实践过程。一、导读 随着ERP的部署架构越来越复杂微应用分布式部署架构在给用户带来高性能高稳定的同时给运维监控、问题排查带来了一定难度特别是后端服务的内部调用由于缺少日志难以快速定位问题根因。借助链路追踪并结合异常日志所记录的链路id可以方便定位整个异常链路更快找到问题的原因。链路追踪还有一个好处针对性能慢的页面原来很难快速定位到慢的具体点通过借助链路追踪能快速掌握每个请求执行了哪些操作每个操作消耗了多长时间精准定位性能问题。二、链路追踪介绍 分布式系统变得日趋复杂越来越多的组件开始走向分布式化如微服务、分布式数据库、分布式缓存等使得后台服务构成了一种复杂的分布式网络。 在服务能力提升的同时复杂的网络结构也使问题定位更加困难。在一个请求在经过诸多服务过程中出现了某一个调用失败的情况查询具体的异常由哪一个服务引起的就变得十分抓狂问题定位和处理效率是也会非常低。 分布式链路追踪就是将一次分布式请求还原成调用链路将一次分布式请求的调用情况集中展示比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。 目前业界的链路追踪系统如Twitter的ZipkinUber的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/// 定义监听的名称/// /summarypublic const string DiagnosticListenerName RedisDataDiagnosticListener;/// summary/// 前缀/// /summarypublic const string CacheDataPrefix Mysoft.Map6.Cache.;/// summary/// 执行前事件名称/// /summarypublic 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,IOptionsGenericEventOptions 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扩展实践