为了减少由于单个请求挂掉而拖垮整站的情况发生,给所有请求做统计是一个不错的解决方法,通过观察哪些请求的耗时比较长,我们就可以找到对应的接口、代码、数据表,做有针对性的优化可以提高效率。在ASP。网网络美国石油学会(American Petroleum Institute)中我们可以通过注册一个委托处理程序来实现该功能。那在ASP。网核心中该如何实现呢?
一:比较ASP。网网络美国石油学会(American Petroleum Institute)和ASP。网核心的请求管道
观察这两张图,可以发现他们非常的相似,都是管道式的设计,在ASP。网网络美国石油学会(American Petroleum Institute)中,我们可以注册一系列的委托处理程序来处理请求上下文HttpRequestMessage,在asp.netcore中,我们可以注册一系列中间件来处理请求上下文,他们两者从功能和意义上是非常相似的,我这里这里不会详细介绍各自的管道是如何的(这样的文章非常多,博客园随处可见),他们都完成了类似中间件的功能,只是在代码设计上有一点区别。
我们先看一段代码,新建一个ASP。网网络美国石油学会(American Petroleum Institute)项目,添加几个DelegatinHandler
然后在全球的中注册
公共类删除句柄1 :删除句柄{受保护的覆盖异步任务http预响应消息发送异步(Httprequestmessage请求,cancelationtoken cancelationtoken){ Trace .写入行(' delegatinghandler 1 hashcode : '这个.GetHashCode());追踪WriteLine('DelegatingHandler1)基' InnerHandler HashCode: '基InnerHandler。GetHashCode());追踪写入行(‘delegatinghandler 1 start’);定义变量响应=等待基数发送异步(请求,取消令牌);追踪写入行(‘delegatinghandler 1 end’);返回响应;} }公共类委托处理程序2 :委托处理程序{受保护覆盖async taskshttpresponsemessage发送异步(Httprequestmessage请求,cancelationtoken cancelationtoken){ Trace .写入行(' delegatinghandler 2 hashcode : '这一行.GetHashCode());追踪WriteLine(' delegatinghandler 2 base内部处理程序hashcode : ' base .InnerHandler。GetHashCode());追踪写入行(‘delegatinghandler 2 start’);定义变量响应=等待基数发送异步(请求,取消令牌);追踪写入行(‘delegatinghandler 2 end’);返回响应;} }公共类委托处理程序3 :委托处理程序{受保护的覆盖async taskshttpresponsemessage发送异步(Httprequestmessage请求,cancelationtoken cancelationtoken){ Trace .写入行(' delegatinghandler 3 hashcode : '这个.GetHashCode());追踪WriteLine(' delegatinghandler 3 base内部处理程序hashcode : ' base .InnerHandler。GetHashCode());追踪写入行(‘delegatinghandler 3 start’);定义变量响应=等待基数发送异步(请求,取消令牌);追踪写入行(‘delegatinghandler 3 end’);返回响应;} }修改一下值控制器
公共类WebApiApplication :系统网络。HttpApplication { protected void Application _ Start(){区域注册.registerelaris();全局配置。配置(网络配置.注册);FilterConfig .RegisterGlobalFilters(全局筛选器。过滤器);路由图。注册路由(路由表。路线);BundleConfig .注册捆绑包(BundleTable .捆绑包);全局配置配置。messagehandlers。add(new delegatinghandler 1());全局配置配置。messagehandlers。add(new delegatinghandler 2());全局配置配置。messagehandlers。add(new delegatinghandler 3());} }启动后输入路径/API/值,我们可以在对的输出栏看到下面这些内容
公共类值控制器: APi控制器{//GET APi/values public IEnumerablesting GET(){ Trace .写行('/API/values ');定义变量处理程序=这个。配置消息处理程序;返回新字符串[] { 'value1 ',' value 2 ' };} }启动后输入路径/API/值,我们可以在对的输出栏看到下面这些内容
删除句柄1哈希代码: 58154627删除处理器1基本内部处理程序哈希代码: 35529478 delegatinghandler 1 startdelegatinghandler 2哈希代码: 35529478删除处理器2基本内部处理程序哈希代码: 4742476删除处理器2开始删除处理器3哈希代码: 47422
输出中我们可以看到删除句柄一的InnerHandler是删除句柄2,以此类推,在删除句柄3的InnerHandler处理请求的时候就转发到了相关控制器,这里和。磊科中的中间件非常相似,在。磊科中间件顺序是RequestServicesContainerMiddleware(给请求上下文绑定容器)-身份验证中间件(认证)- RouterMiddleware(路由以及MVC)
如果我们在值控制器中观察表达式这个。配置消息处理程序还可以看到最终处理请求的是一个HttpRoutingDispatcher,最也是是分配到路由以及控制器来处理的,按照如此方式我们可以很容易在asp.netweb api中对请求统计。这里是比较简陋的,对此我们可以记录客户端和服务器更详细的信息,包括互联网协议(互联网协议)地址,http状态码,是否是认证用户等等,但是这篇主要是以asp.netcore为主的,所以这里就不详细写下去了。
公共类application insight : DelegatingHandler {受保护的覆盖async taskshttpresponsemessage send async(http请求消息请求,canceltintoken canceltintoken){ var秒表=新秒表();秒表start();定义变量响应=等待基数发送异步(请求,取消令牌);秒表stop();//停止计时器,并记录} }公共分部类启动{公共无效配置(IAppBuilder应用){全局配置.配置。messagehandlers。添加(新的ApplicationInsight());} }二:asp.netcore中间件股市分析实现请求监控
先看统计结果,开始开始时间,时间是请求消耗时间(毫秒),authenicate是认证通过的模式,使用股市分析自定义字段也是非常方便的
先说一说遇到的问题
(1)NLog记录一张以上的表如何实现,应为首先会有一个一般性的日志表(称他为日志),并且这些统计不对写到原木表
(2)使用股市分析自定义字段LayoutRenderer没有类似网络.框架中的系统。网络。当前
(3)使用使用中间件无法在让我们的中间件成为第一个中间件
(4)实现忽略记录的方法,肯定有一些接口是不希望记录的,所以这个也要实现
股市分析配置
这里只列出了部分内容,github地址在最后,数据库是apiinsight表示请求统计日志是一般性的日志,debughelper可以加快我们调试时日志的检索速度
目标!-黑洞忽略的日志-target xsi :类型=' Null '名称='黑洞'/!-文件日志-目标xsi :类型=' File ' name=' debughelper ' fileName=' $ { var : root } Logs debug _ helper。log ' layout=' $ { long date } | $ { event-properties : item=event id .id } | $ { logger } | $ {大写: $ { level } } | $ { message } $ { exception } '/!- apiinsight api统计-目标名称=' API insight ' xsi :类型=' Database ' DBProvider=' MySQL .数据。MySqlClient。MySql连接,MySQL .data ' connectionString=' $ { var : connectionString } '/target!-日志-目标名称=' log ' xsi 3360类型=' Database ' DBProvider=' MySQL .数据。MySqlClient。MySql连接,MySQL .数据connectionString=' $ { var : connectionString } '/target/targets在启动中
公共void Configure(IAP presentonbuilder应用程序,IHostingEnvironment env){ //省略了其他配置//全局的上下文对象应用程序UseGlobalHttpContext();//省略了其他配置日志管理器。配置=新的XmlLoggingConfiguration(路径。合并(环境.ContentRootPath,' nlog。config’);日志管理器。配置。变量['根]=env .ContentRootPath日志管理器。配置。变量['连接字符串']=配置GetConnectionString(' DefaultConnection ');}自定义字段都是通过LayoutRenderer实现,由于自定义字段有很多,这里只列出了一个开始时间是如何查询的,这个时间是在我们注册的第一个中间件执行引起方法的时候写进HttpContext .项目的
[LayoutRenderer(' API insight-start ')]公共类startapinsignightrenderer : LayoutRenderer {受保护的覆盖void Append(StringBuilder builder,LogEventInfo log event事件){ var httpContext=HttpContextProvider .当前;if(HttpContext==null){ return;} var _ apinsightskeys=HttpContext .请求服务。getserviceiapiinvisionskeys();if (httpContext!=null) { if (httpContext .物品。tryGetVaLue(_ Apisinghtskeys .StartTimeName,out var start)==true) { builder .追加(开始. ToString());} } } }NLog规则,很容易理解日志统计只记录干杯命名空间下的日志
规则!-需要记录的日志-记录器名称='干杯* "最低级别='跟踪'写入='应用编程接口洞察'/记录器名称='网络应用程序.*”最小级别=“信息”写入=“日志”/记录器名称=“*”最小级别=“跟踪”最大级别=“调试”写入=“调试程序”/啊!-忽略的日志-记录器名称='微软* '最小级别='跟踪'写入='黑洞'最终='真'/规则核心apinsight中间件中间件
公共类apinsightmiddleware { private readonly request delegate _ next;private readonly IServiceProvider _ ServerProvider;private readonly iapiniewskeys _ apinsightskeys;私有只读ilogageapisingmiddleware _ logger;私有HttpContext _ httpContextpublic apisingtimedia(request delegate next,iseservice provider service provider,ilogageapisingtimedia logger){ _ next=next;_ ServerProvider=ServiCeProvider;_ Apisinghtskeys=_ ServerProvider .getserviceiapiinvisioskeys();_ logger=logger}公共异步任务调用(Http context Http context){ _ Http context=Http context;定义变量标志=SetVaLues();wait _ next(HttpContext);if(flag==true){ ApiInSight();} } //省略了其他的代码}很好理解,在执行下一个中间件之前调用设置值开始计时,下一个中间件执行成功开始统计并写入日志(或者忽略不写)。现在他是ASP。NET coremvc的第一个中间件了,好处就是更符合这个中间件本身的所做的事情了,但是带来的问题就是httpContext .RequestService是null,因为RequestService是在RequestServicesContainerMiddleware这个中间件写进去的,在者其实很多地方我们都需要HttpContext,并且目前微软还没有给我们定义一个静态的HttpContext。
静态的上下文对象
上下文对象是通过单例IHttpContextAccessor提供的,当上下文对象创建的时候就会赋值给他,当请求到达中间件这个管道的时候,HttpContext就已经存在于IHttpContextAccessor了,并且和引起参数列表中的上下文对象是一致的(同一个请求中),问题在于RequestServicesContainerMiddleware这个中间件没有执行就没有容器,并且很多时候我们都要用到容器,所以就模仿源码在这里都加进去了。
公共静态类httpcontextprovider { private static IHttpContextAccessor _ accessor;私有静态IserviceScopeFactory _ ServiceScopeFactory;公开静态微软AspNetCore。Http。HttpContext当前{ get { var context=_ accessor?HttpContextif(上下文!=null){ var replacementFeature=new requestservicesffeature(_ service scope factory);语境。功能。setiseservice providers feature(replacementFeature);返回上下文;}返回null} }内部静态void ConfigureAccessor(IHttpContextAccessor,IServiceScopeFactory service scopefactory){ _ accessor=accessor;_ ServiceScopeFactory=ServiceScopeFactory;} }公共静态类HttpContextExtenstion {公共静态void AddHttpContextAccessor(此为iServiceCollection services){ services } .AddSingletonIHttpContextAccessor,HttpContextAccessor();}公共静态IApplicationBuilder UseGlobalHttpContext(此IApplicationBuilder应用程序){ var httpContextAccessor=app .应用服务。GetRequiredServiceIHttpContextAccessor();var serviceScopeFactory=app .应用服务。getrequiredservice iservicescopefactory();HttpContextProvider .ConfigureAccessor(httpContextAccessor,服务范围工厂);返回app} }我们只需要在启动中使用应用程序.UseGlobalHttpContext();就可以在程序的任何地方得到上下文对象和容器了,肯定会有人说为什么不通过构造函数来获取我们想要的注入呢,因为有些第三方框架或这某些地方我们不能使用容器获取服务,比如这里股市分析的自定义字段使用的LayoutRenderer就无法通过构造器得到我们想要的服务。
第一个中间件
在启动的安装使成形方法中目前还没发现如何注册第一个中间件,因为安装使成形方法始终是在IStartupFilter这个接口之后执行的,这也提供了我们让自己的中间件成为第一个中间件的可能。可能这样做并不是特别有必要,甚至是没有意义的,但是实现的过程确实很有意思的。这里在启动中的方法配置服务注册我们的中间件。
public void ConfigureServices(IServiceCollection services){ services .addapiSignations服务addMVc();}具体的
公共静态类apisinghtsservicecollectionextensions { static readonly string stop watch name=' _ _秒表_ _ ';静态只读字符串startTimeName=' _ _ start _ _///摘要///注册和应用程序接口监控相关的服务,中间件////summary////param name=' services '/param public static void addapisings(此为IServiceCollection services){ services } .AddSingletonIApiInsightsKeys(新的apinsightskeys(停止观察名称,开始时间名称));服务第一个注册开始过滤器,请求开始过滤器(服务集合服务扩展).AddTransientIStartupFilter,requestapinsightbeginstatupfilter);服务addsingletionrequestsauthinature,defaultrequestsauthenticate} }这里注册了三个服务
iapiinvisionskeys
定义了存储在HttpContext .项目中的键值对的名称
公共接口iapiniewskeys { string StOpwatchname { get;}字符串StartTimeName { get } } irequestisauthentice
///summary////验证请求用户是否经过身份验证////summary公共接口irequestisauthentice {///summary////返回经过身份验证的方案////summary////返回/返回任务字符串isauthenticeasync();///summary////返回经过身份验证的用户名////summary////返回/返回任务字符串authenticated username();}就身份验证而言,不同的开发人员可能会使用不同的身份验证方法,可能基于Asp.Net core Authentication中间件的身份验证方法,也可能基于其他身份验证方法,如用户定义的令牌,或具有单一登录服务器,或会话。事实上,Asp.Net核心的认证中间件也可以帮助我们实现基于restful的令牌认证。所以定义了它,默认实现是基于身份验证中间件的。
IStartupFilter
鉴于这是一种非常特殊的注册方式,用户定义的FirstRegister方法,实际上是Asp。NetCore有像IStartup这样的内置服务,这些服务都是在Configure of Startup之前执行的,所以我们必须使用这个服务来使我们的中间件成为这里的第一个中间件。FirstRegister代码也很容易理解。由于在主机启动之前内部注册了多个iStartups,最后会按顺序配置IApplicationBuilder,所以我们只能将自己的中间件注册到第一个StartupFilter的IApplicationBuilder中,这可以通过改变ServiceCollection中服务的顺序来实现。虽然不是必须的,但是我们可以观察启动的配置方法和接口StartupFilter(和IHostingStartup)的执行顺序。
公共类requestapisinghtbegininstartupfilter : IStartupFilter { public ActionIApplicationBuilder Configure(ActionIApplicationBuilder next){ return builder={ builder }。usemiddlewarerequestapisingbeginmiddleware();下一个(构建器);};}}忽略的方法
[属性用法(属性目标。方法,AllowMultiple=false,Inherited=true)]公共类NoInsightAttribute : attribute { }在ApiInsight方法中,将调用IsIgnore来检测该方法是否被标记为NoInsightAttribute,如果是,该方法将被忽略。建议在这里使用特征路由,原因有二。首先,特征路由不需要使用IActionSelector接口再次查找匹配方法。第二,在restful api中,将特征路由与HttpMethodAttribute标签相结合,可以使方法更加简洁,相同的接口名称可以通过不同的请求方法达到不同的目的。
private bool IsIgnore(){ var action descriptor=GetSelectedActionDescriptor()作为ControllerActionDescriptorif(action descriptor==null){ return false;} else { var noin sight=action descriptor。method info . GetCustomAttributeNoInsightAttribute();不要向右返回!=null}}节目地址: https://github.com/cheesebar/ApiInsights
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。