从壹开始前后端分离 40 || 完美基于AOP的接口性能分析

旁白音:本文是不定时更新的.net core,当前主线任务的Nuxt+VueAdmin教程的 nuxt.js 之 tibug项目已上线,大家可以玩一玩:http://123.206.33.109:7090,具体的部署教程会在下周发表。

缘起

哈喽大家周五好呀,今天是一个不定时更新的文章,是很简单的一篇文章,大家应该都能看懂,主要包含了两个内容,一个是对AOP编程的进一步的理解(其中还有和过滤器比较),第二个就是一个简单的小插件——记录接口的调用时间调用情况,也就是很简单的性能记录,这个时候你肯定不要和 Metricss+influxdb+grafana 作比较了,它们功能虽然很大,但是用起来笨重,咱们这种入门级别的小项目暂时先不用这个了,说到这里,昨天有小伙伴留言,说要不要在项目中增加消息队列 ReditMQ ,我正在考虑中,看看如何使用,好啦废话不多说,先来几个小问题,热热身:

1、在平时开发的时候,大家是如何记录当前 <接口/方法> 的调用时间的?// 手动写起止时间相减

2、如何对异常进行处理的,这里有 <api层的异常>,以及 <service 层的异常> 的?// 加 try catch

3、如何快速的找到当前接口的错误信息?// 查看日志记录

大家先带着这几个问题自己想一想,是不是和我的绿色解决方案一致,要是有更好的办法也帮忙提给我,不胜感激!

不过!今天肯定不会用这些解决方案的,今天玩儿一个新花样,应该也会有人玩儿过,别着急,咱们往下看。

先来个实现图,预热下:

一、复习篇——我们把AOP用在了哪里?

时间是很快,我也已经从第一个专题,写到了第三个专题,还记得当时第一次写AOP的时候《框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存》,很多很多的小伙伴不是很明白,也不知道应用场景到底在哪里,完全不了解落地几何,现在也能在群里,时不时看到有小伙伴用到AOP编程,感觉很开心,至少帮到了一些人了,这就是最大的欣慰!那咱们在Blog.core 项目中,到底如何运用了 AOP 呢?

1、切面缓存

这一块相信已经有小伙伴知道,并且用到了,我也是在很多地方使用到了,比如在基于策略的权限认证文章中《JWT完美实现权限与接口的动态分配》,通过了对角色模块的切面缓存,很好的实现了快速授权的目的,不仅代码整洁,而且功能也实现了:

 // 将最新的角色和接口列表更新
 var data = await _roleModulePermissionServices.GetRoleModule();
 var list = (from item in data
             where item.IsDeleted == false
             orderby item.Id
             select new PermissionItem
             {
                 Url = item.Module?.LinkUrl,
                 Role = item.Role?.Name,
             }).ToList();
 // 通过AOP缓存获取角色模块信息
 [Caching(AbsoluteExpiration = 10)]
 public async Task<List<RoleModulePermission>> GetRoleModule()
 {
     var roleModulePermissions = await dal.Query(a => a.IsDeleted == false);
     //.......
     return roleModulePermissions;
 }

另外还有在当前第三系列教程中,获取Bug信息的时候,也用到了切面缓存(所以,如果你用了文章开头的 tibug 系统,提交了一个 bug,但是没有立刻显示处来,就是这个原因,10分钟缓存):

综上所言,经过多次使用,我个人表示真的是一个神器,在完全不露痕迹的情况下,实现了缓存,是不是很好用?

这个时候你会问,单独为了缓存的话,AOP不是很透彻,那还有其他的用处么?请往下看。前提是上边的这种基于AOP的缓存你要看懂了,先在脑子里回顾下整体流程。

2、切面日志

上边咱们说了缓存,我个人感觉还有一个很大的用户就是切面日志,这个具体的内容以前已经说过了,这里就不多说了,想了解原理和详细说明,请看《AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存》,这里只是复习下流程:

        public void Intercept(IInvocation invocation)
        {
            //记录被拦截方法信息的日志信息
            var dataIntercept = $"{DateTime.Now} " +
                $"当前执行方法:{ invocation.Method.Name} ";

            //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的
            invocation.Proceed();

            dataIntercept += ($"方法执行完毕,返回结果:{invocation.ReturnValue}");

            #region 输出到日志文件
            #endregion
        }

只需要我们 ServiceConfigure 中开启服务以后,就可以在指定文件中,看到每次的切面接口调用情况,注意这里是 service 层的,不是 controller 的日志,这个时候就是 AOP 和过滤器 Filter 的区别了:

1、Filter过滤器是基于当前Http请求的,也就是接口层面的,颗粒度比较大;

2、而AOP是基于服务切面的,是 Service 层的请求,颗粒度比较小;

那既然AOP能记录调用日志,能捕获异常么,上次群里有一个小伙伴问到了,这里就点名表扬了,挺棒的,能自己思考,那如何捕获呢?

3、切面异常

在平时的开发中,我们经常会遇到各种 Bug ,在 controller 里的错误就不说了,编译的时候基本都能看出来,但是很多 service 层的错误,真是难找,比如我故意写的这个bug,一个不重要的方法:

  public void NoImportantMethod()
  {
      int a = 1;
      int b = 0;
      int c = a / b;
  }

这个错误是如何捕获的,大家还记得么,就是我们用全局变量异常过滤器 Filter 捕获的《三十五║ 完美实现全局异常日志记录》:

我们平时可能会在 api 中使用这样的service层方法,然后下边也会有其他的一些方法,因为这个方法不重要,比如仅仅是阅读数量+1,那我们就不能让当前接口崩了

 public async Task<MessageModel<Response>> Get()
 {
     var data = new MessageModel<Response>();

     // 一个不重要的方法
     _advertisementServices.NoImportantMethod();

     // 核心功能:说爱你
     Love love = New Love();
     love.SayLoveU();

     return data;
 }

这个我们我们会在 NoImportantMethod() 这里报异常,直接崩溃出去,你感觉这样的设计合理么?我们不能因为一个不重要的动作就不说核心的 我爱你 了吧??,所以我们一般就会使用 try catch 的方法,把我们认为会出错的地方包括住,比如这样:

 public async Task<MessageModel<Response>> Get()
 {
    var data = new MessageModel<Response>();

    try{
     // 一个不重要的方法
     _advertisementServices.NoImportantMethod();
    }catch{}

     // 核心功能:说爱你
     Love love = New Love();
     love.SayLoveU();

     return data;
 }

这样虽然问题解决了,但是总感觉很难受,太丑了,会导致我们所有的接口都写满了这个 try try try,哦no!那咋办么,别慌,还记得我们的AOP记录日志么,既然能记录日志,照样能记录异常!

没错就是这里:

 try
 {
     invocation.Proceed();
 }
 catch (Exception e)
 {//执行的 service 中,捕获异常
     dataIntercept += ($"方法执行中出现异常:{e.Message + e.InnerException}");
 }

我们看看效果:

然后很顺利的捕获到了异常,并且主程序也走了下去,我们就可以找到我们的日志文件,然后看看错误:

20190118 11:40:50 当前执行方法:ReturnExp 参数是:
方法执行中出现异常:Attempted to divide by zero.方法执行完毕,返回结果:

到这里!我们的文章开头提问的第二个方法已经顺利解决!我现在在公司的项目中就是用的这种切面方法,来进行处理的,把那些不重要的方法给走下去,虽然可能会出现,提交了订单,但是一直提交不上的问题,但是至少保证不会让页面崩了的尴尬情况。

到了这里不知道你是否满意了呢,别慌,刚刚我们是如何来查看错误的?是通过找日志是吧,然后还需要对应的时间呀,接口方法呀啥的,如果日志信息很多,那茫茫bug,如何找到你心仪的那一个呢,这样,文章开头的第三问就出来了,这里先不说,先说下第一个问题。请往下看。

二、接口执行时间分析——MiniProfiler

1、平时我们的是如何测接口时间的

A:我们平时在开发接口的时候,总想看看一个接口到底执行了多久,甚至想看看某一个service 方法执行了多长时间,这个时候,我以前都是通过 断点调式的方法,来看执行时间,比如这样:

(这里说下我用的 Sqlsugar 的批量添加,100条,0.4s,速度还行,给凯旋兄打给广告??)

B:或者直接就是写个起止时间方法,来判断下,比如这样(图片来自群管理DX):

但是我们有那么多的接口,那么多的方法,总不能都这么 Debug 调试吧,也不能都写个方法吧,

C:我们可以使用AOP来进行每次方法的时间记录

就算是用上边的 AOP 写了一个,还是得查看日志不是,而且也只能是service层的,那api层还得写过滤器了,这个时候,有一个插件可以帮我们省去查看日志的诟病,就是它——MiniProfiler,这个具体的功能呢,我就不说了,大家自行百度即可,就是一个小插件,我这里就说下如何使用:

2、如何使用 MiniProfiler

使用方法很简单,首先我们需要引入nuget包:

 Install-Package MiniProfiler.AspNetCore.Mvc

然后,在startup.cs 中配置服务ConfigureServices:

  services.AddMiniProfiler(options =>
      {
          options.RouteBasePath = "/profiler";//注意这个路径要和下边 index.html 脚本配置中的一致,
          (options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10);

      }

最后,调用下中间件即可:

  app.UseMiniProfiler();

当前这个时候还不能使用,我们还需要在 Swagger 中进行配置,以便它能在 swagger 中使用。

3、在 Swagger 中配置 MiniProfiler

上边我们在配置中已经启动了服务,接下来就需要设置如何在 swagger 中展示了,这个时候我们就需要自定义我们的swagger主页了,以前我们是用的默认的index.html,现在咱们需要自定义一个:

从官网 Github 上,下载最新的 index.html:https://github.com/swagger-api/swagger-ui/blob/master/dist/index.html(官方的应该是这个,和我的有一丢丢不一样,应该也可以用)

然后添加到项目中,我是放到了根路径了:

这个时候记得要把这个文件设置成嵌入资源的类型:

接下来,在 Index.html 文件中,增加配置脚本(我是在顶部写的,Head应该也可以,其他的没实验):

<script async="async" id="mini-profiler" src="/profiler/includes.min.js?v=4.0.138+gcc91adf599"
        data-version="4.0.138+gcc91adf599" data-path="/profiler/"
        data-current-id="4ec7c742-49d4-4eaf-8281-3c1e0efa748a" data-ids="" data-position="Left"
        data-authorized="true" data-max-traces="15" data-toggle-shortcut="Alt+P"
        data-trivial-milliseconds="2.0" data-ignored-duplicate-execute-types="Open,OpenAsync,Close,CloseAsync">
</script>

<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
........
........

具体的参数请自行研究吧,基本都能看懂,比如版本,/profiler的路径,position的位置显示(我是左边),authorized的是否权限,max-traces最多显示多少条(15)等等。

然后我们修改下中间件去调用我们这个 index.html 页面:

app.UseSwaggerUI(c =>
{
    typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version =>
    {
        c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}");
    });    // 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:解决方案名.index.html
    c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html");

});

最后,记得一定要配置了使用静态资源文件的中间件:

 app.UseStaticFiles();

好啦,现在已经配置好了 MiniProfiler 和 Swagger了,最后就是如何使用了:

4、在接口中使用 MiniProfiler

这里我在BlogController 的Get方法中,使用:

       public async Task<object> Get(int id, int page = 1, string bcategory = "技术博文")
        {            ......// 你可以用这种包括的形式
            using (MiniProfiler.Current.Step("开始加载数据:"))
            {
                if (redisCacheManager.Get<object>("Redis.Blog") != null)
                {
                    // 也可以直接这么写
                    MiniProfiler.Current.Step("从Redis服务器中加载数据:");
                    blogArticleList = redisCacheManager.Get<List<BlogArticle>>("Redis.Blog");
                }
                else
                {
                    MiniProfiler.Current.Step("从MSSQL服务器中加载数据:");
                    blogArticleList = await blogArticleServices.Query(a => a.bcategory == bcategory);
                    redisCacheManager.Set("Redis.Blog", blogArticleList, TimeSpan.FromHours(2));
                }
            }
           ......
        }

最后的效果就是这样的(这里说明下:我有时候在F5启动项目,然后第一次点击某一个接口的时候,当前接口分析报告会没有,再点击这个接口,报告就好了,情况以后也不会出现了,如果你也遇到了不要慌,属于正常问题):

是不是感觉挺好的!这样的时间都有了,然后还记得上边配置的 /profiler 么,我们点击 share 就能看到了,这里不细说了,大家自己玩一玩。现在有一个问题就是,我总不能每一个 api 接口都这么写吧,多麻烦呀!机智如你,这个时候 AOP 日志记录又排上用场了!

5、切面 MiniFilter 分析

很简单,只需要在 AOP 日志记录中,配置 MiniProfiler 即可:

 try
 {
     // 就是这里!!
     MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> ");

     invocation.Proceed();
 }
 catch (Exception e)
 {
     //执行的 service 中,捕获异常
     dataIntercept += ($"方法执行中出现异常:{e.Message + e.InnerException}");
 }

这样就在任何的接口中,都能看到这个调用信息了!

是不是现在更加体会到了 AOP  的好处!

就这样,咱们文章开头的第一个问题就这样解决啦!目前是第一、第二两个问题都解决了,就剩下第三个问题了,如果查看错误日志呢,目前我们还是在日志里查看的,别慌,虽然 MiniProfiler没有这个功能,但是我自己奇思妙想了一个,请往下看。

三、结合AOP+MiniProfiler实现异常可视化

刚刚上边咱们也说到了,可以使用 AOP 进行异常的捕获记录,只不过需要在 log 日志文件中,查看。而 miniprofiler 的功能竟然正好是时间的分析展示,那我们可不可以融合下两者呢!

很简单!我们只需要在 AOP 的切面异常catch中,还有 全局Filter 异常中,将错误信息融入到 MiniProfiler即可:

// 这个是AOP中
 try
 {
     MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> ");
     invocation.Proceed();
 }
 catch (Exception e)
 {
     //执行的 service 中,收录异常
     MiniProfiler.Current.CustomTiming("Errors:", e.Message);
     //执行的 service 中,捕获异常
     dataIntercept += ($"方法执行中出现异常:{e.Message + e.InnerException}");
 }

 //这个是全局异常处理中
 public void OnException(ExceptionContext context)
 {
     //...................不重要内容...............

     MiniProfiler.Current.CustomTiming("Errors:", json.Message);

     //采用log4net 进行错误日志记录
     _loggerHelper.Error(json.Message, WriteLog(json.Message, context.Exception));

 }

这个时候,我们查看下效果,还记得上边咱们说到的那个栗子么,就是为了一个不重要的方法,而不能说出核心功能 “我爱你” 的那个栗子,咱们现在再看看效果:

是不是很方便!不仅能查看出所有的接口调用时间记录,还能查看错误信息!这对于我们平时开发还是很有帮助的!

文章开头的第三个问题也完美解决!

四、结语

好啦,今天的讲解就到这里了,主要说了分析了AOP切面编程的大作用,然后讲解了 MiniProfiler 插件的使用,同时对异常的处理,极大的帮助开发者定位错误已经信息处理。

-- ♥ -- ♥ -- ♥ -- ♥ -- ♥ -- ♥ --

原文地址:https://www.cnblogs.com/laozhang-is-phi/p/10287023.html

时间: 2024-09-30 01:33:58

从壹开始前后端分离 40 || 完美基于AOP的接口性能分析的相关文章

从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十三║Vue实战:Vuex 其实很简单

系列教程一目录:.netcore+vue 前后端分离 系列教程二目录:DDD领域驱动设计 系列教程三目录:Nuxt.js TiBug系统 系列教程四目录:VueAdmin 后台管理系统 系列教程五目录:IdentityServer4 授权服务器 本文梯子 前言 零.今天要完成实战1中的红色部分 一.常见的 Vue 表单提交是如何设计的? 1.表单.按钮等在一个组件内 2.按钮在父组件.表单在单独的子组件内 二.通过 $emit 修改父组件数据 1.在原来代码里 About.vue 修改成 For

从壹开始前后端分离【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之七 || API项目整体搭建 6.2 轻量级ORM

代码已上传Github,文末有地址 书接上文:<从壹开始前后端分离[ .NET Core2.0 Api + Vue 2.0 + AOP + 分布式]框架之六 || API项目整体搭建 6.1 仓储>,我们简单的对整体项目进行搭建,用到了项目中常见的仓储模式+面向接口编程,核心的一共是六层,当然你也可以根据自己的需求进行扩展,比如我在其他的项目中会用到Common层,当然我们这个项目接下来也会有,或者我还会添加Task层,主要是作为定时项目使用,我之前用的是Task Schedule,基本能满足

从壹开始前后端分离【 .NET Core2.0 Api + Vue 3.0 + AOP + 分布式】框架之九 || 依赖注入IoC学习 + AOP界面编程初探

代码已上传Github,文末有地址 说接上文,上回说到了<从壹开始前后端分离[ .NET Core2.0 Api + Vue 2.0 + AOP + 分布式]框架之八 || API项目整体搭建 6.3 异步泛型+依赖注入初探>,后来的标题中,我把仓储两个字给去掉了,因为好像大家对这个模式很有不同的看法,嗯~可能还是我学艺不精,没有说到其中的好处,现在在学DDD领域驱动设计相关资料,有了好的灵感再给大家分享吧. 到目前为止我们的项目已经有了基本的雏形,后端其实已经可以搭建自己的接口列表了,框架已

从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十六 ║ Vue前篇:ES6初体验 &amp; 模块化

缘起 昨天说到了<从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║ Vue前篇:JS对象&字面量&this>,通过总体来看,好像大家对这一块不是很感兴趣,嗯~~这一块确实挺枯燥的,不能直接拿来代码跑一下那种,不过还是得说下去,继续加油吧!如果大家对昨天的小demo练习的话,相信现在已经对JS的面向对象写法很熟悉了,如果嵌套字面量定义函数,如何使用this关键字指向.今天呢,主要说一下ES6中的一些特性技巧,然后简单说一下模块化的问题,好啦,开始今天的讲

从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十七 ║Vue基础:给博客首页加花样(二)

回顾 今天来晚辣,给公司做了一个小项目,一个瀑布流+动态视频控制的DEMO,有需要的可以联系我,公司的项目就不对外展示了(一个后端程序员真的要干前端了哈哈哈). 书接上文,昨天正式的开始了Vue的代码的学习,简单的通过一些假的数据来展示了下个人博客的首页列表,不知道大家是否还记得昨天讲的什么,如果不太清楚呢,可以再回顾下<从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十七 ║Vue基础:使用Vue.js 来画博客首页(一)>,我们主要说到了,Vue的核心语法是什么,MVVM

前后端分离实践:基于vue实现网站前台的权限管理

Javascript做为当下的热门语言,用途很广泛,从前端到后端处处可见其存在,该技术如今在我们项目内部也大量使用来开发诸如CMS系统以及其他其他一些数据分析系统的前端页面,为此个人非常感兴趣并将其作为帽子卡的扩展内容来进行课余学习. Javascript框架鳞次栉比,但基本原理大致相同,因此选用国内人开发的vue.js进行一个初步的尝试.学习vue.js也一周多的时间了,说起vue的主要用法,无外乎Declarative Rendering.Component System.Client-si

从壹开始前后端分离[.netCore 不定期 ] 36 ║解决JWT权限验证过期问题

缘起 哈喽,老张的不定期更新的日常又开始了,在咱们的前后端分离的.net core 框架中,虽然已经实现了权限验证<框架之五 || Swagger的使用 3.3 JWT权限验证[修改]>,只不过还是有一些遗留问题,最近有不少的小伙伴发现了这样的一些问题,本来想着直接就在原文修改,但是发现可能怕有的小伙伴看不到,就单发一条推送吧,所以我还是单写出一篇文章来说明解决这些问题,希望对无论是正在开发权限管理系统,还是平时需要数据库动态绑定权限分配的你有一些启发和思考.今天咱们注意解决这三个问题: 1.

从壹开始前后端分离【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之六 || API项目整体搭建 6.1

书接上文:前几回文章中,我们花了三天的时间简单了解了下接口文档Swagger框架,已经完全解放了我们的以前的Word说明文档,并且可以在线进行调试,而且当项目开始之中,我们可以定义一些空的接口,或者可以返回假数据,这样真正达到了前后端不等待的缺陷,还是很不错的,当然,这离我说的前后端分离还是相差甚远,今天呢,我们就简单搭建下我们的项目架构. 本项目是我自己的一个真实项目,数据都是真实的,之前搭建过一个MVC + EF Code First的项目,本项目就是基于这个了,前一段时间我已经搭建起来了,

从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十一║Vue实战:开发环境搭建【详细版】

系列教程一目录:.netcore+vue 前后端分离 系列教程二目录:DDD领域驱动设计 系列教程三目录:Nuxt.js TiBug系统 系列教程四目录:VueAdmin 后台管理系统 系列教程五目录:IdentityServer4 授权服务器 本文梯子 缘起 零.今天要完成左下角红色的部分 A.Vue 常见的IDE —— 我是开发工具,干活的都是我 1.VsCode 2.Webstorm 3.Atom B.安装Nodejs环境 —— 我是运行环境,没我不行 C.安装 npm / cnpm ——