zoukankan      html  css  js  c++  java
  • Spring-AOP实践

    Spring-AOP实践

    公司的项目有的页面超级慢,20s以上,不知道用户会不会疯掉,于是老大说这个页面要性能优化。于是,首先就要搞清楚究竟是哪一步耗时太多。

    我采用spring aop来统计各个阶段的用时,其中计时器工具为StopWatch。

    文章结构:

    1. 遇到的问题
    2. 创建项目
    3. AOP-HelloWorld
    4. 时间统计
    5. bug
    6. final
    7. 压力测试
    8. 源码

    其中,遇到的问题:

    1.少包aspectjweaver

    添加依赖后才可以使用@Aspect

    2.环绕通知加入多个point

    刚开使用&&连接多个point,傻傻的看不到调用,忽然看到要用||才对

    3.监听时间工具StopWatch每次只能启动一个,一定要关闭后才能启动下一个。

    而我想要测试controller->service->repository各个阶段用时显然很不适应。因为使用同一个stopwatch对象来保存时间,而stopwatch每次只能记录一个时间段作为总时间的一部分,不存在时间嵌套关系(这个以后可以考虑想想别的方案)。controller开始后执行部分验证逻辑或是其他,然后调用service,这时候service启动定时会失败,因为controller的计时器还没关,因此需要先关掉controller的计时器。这样会导致controller的计时部分仅仅是调用service之前的时间,service返回值之后,controller再进行某些处理的时间并没有统计。显然,我需要一个卡表的时间统计设计,即进入controller开始计时,调用service后开始service计时,以此类推,最后获得controller总时间Tc,service总时间Ts,repository总时间Tr.所以时间统计应该如图1:

                    

                   图一                                                                 图二

    这样的话,我应该分别获得Tc,Ts,Tr的时间,然后计算百分比或者别的统计工作。也就是说,log仅仅是log,记录而已,想要得到一个统计结果还需要针对数据二次开发,这就需要将时间存储起来或者进行日志挖掘,然而这统计的工作会越来越复杂,最终我都不知会加多少任务进去。

    事实上,我们这个页面耗费时间主要是多个webservice调用产生的io耗时,也就是说其实统计一个层面的时间就差不多了。那么,忽略service返回后controller的其他时间,仅仅计算controller开始到调用service的时间为Tc,service开始到调用repository的时间为Ts,如图2,这样利用StopWatch就很容获取时间统计结果。

    4.线程不安全

    我居然使用单例的一个成员变量做状态存储,真是。待改进

    1.搭建项目

    采用spring-boot-aop的框架来搭建。最终结构图如下:

    1.1在idea中,new project-》maven

    1.2初始化pom,添加aop,web,test依赖:

      

    1.3创建启动入口:com.test.spring.aop.Application.java

      

    1.4发现我们需要一个service:

    创建com.test.spring.aop.domain.service.IHelloService

    创建com.test.spring.aop.domain.service.impl.HelloService

      

    1.5发现我们需要一个属性配置文件:

    创建application.yml

     View Code

    1.6这时候可以测试一下启动看看了

    直接启动:运行com.test.spring.aop.Application#main()即可。

    命令行启动:mvn spring-boot:run

    命令行bug启动:mvn spring-boot:run -Drun.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"

    1.7创建测试com.test.spring.aop.ApplicationTest

     View Code

    2.AOP - HelloWorld

    2.1创建com.test.spring.aop.monitor.ServiceMonitor

     View Code

    2.2测试

    测试水平还没开始,所以只能手动测试了,运行com.test.spring.aop.ApplicationTest可以看到在HelloService调用前后插入了日志。

    3.时间统计

    最终,生成类图如下:

    最终获得的时间统计如下:

    复制代码
    2016-07-16 21:25:09.361  INFO 16824 --- [nio-8080-exec-4] com.test.spring.aop.monitor.UserMonitor  : StopWatch 'controller': running time (millis) = 3218
    -----------------------------------------
    ms     %     Task name
    -----------------------------------------
    01001  031%  List com.test.spring.aop.domain.service.IUserService.getAll()
    02000  062%  Object com.test.spring.aop.domain.repository.IConnector.getSthFromRemote()
    00217  007%  List com.test.spring.aop.domain.repository.IUserDao.getAll()
    复制代码

    3.1需要设计一个controller

    创建com.test.spring.aop.web.UserController:

     View Code

    3.2发现需要一个service

    创建com.test.spring.aop.domain.service.IUserService

     View Code

    创建com.test.spring.aop.domain.service.impl.UserService

     View Code

    3.3发现需要一个repository

    创建com.test.spring.aop.domain.repository.IUserDao

     View Code

    创建com.test.spring.aop.domain.repository.impl.UserDao

     View Code

    3.3.1临时添加一个远程调用

    创建com.test.spring.aop.domain.repository.IConnector

     View Code

    创建com.test.spring.aop.domain.repository.impl.Connector

     View Code

    3.4发现需要一个实体类

    创建com.test.spring.aop.domain.entiry.User

     View Code

    3.5完成

    以上就基本把一个web接口搞定了,可以运行测试一下,使用浏览器或者postman访问localhost:8080/user/all就可以获得user列表了。

    3.6加入user时间统计

    创建com.test.spring.aop.monitor.UserMonitor

     View Code

    这里有几点问题:

    1.StopWatch不能重复创建,如果想统计每次controller访问时间,必须在访问前初始化,访问后废除。

    2.这里的时间统计仅仅针对controller入口的统计,也就是说,如果别的调用了service,这个时间是不统计的,因为StopWatch没有初始化。

    3.StopWatch的prettyPrint是一个很好的东东

    4.如果想要向开始那样,完全统计每层耗时,这个设计需要重新设计。当前有这样的想法。controller切点初始化三个或者多个StopWatch;service、repository等分别建立切点并计时;controller执行完毕之后的切点汇总统计数据,销毁各个StopWatch。这样就做到了每个层都统计了。然而问题来了,如果一个controller调用了多个service,显然需要统计所有service耗时和单个service耗时。

    Boo! Big Bug!!!

    今早起来接着看,忽然想起spring bean管理是单例的,而且必须是单例的才可以使用共同的成员变量,但问题来了,spring的controller是多线程的。也就是说,这个切面进入不是排队的,第一个请求过程中第二个请求也是可以进来的。那么,共享成员变量实在是愚蠢的决定。然而选择创建一个类来管理对应一个请求的计时器也不好的,如果并发量超级大岂不是每个人都会产生一个类?

    因此,这个计时器必须是一个过程量,可以在指定区间(即controller执行期间)生存,而后销毁。对应的,显然是每个并发请求会至少产生一个类。也就是说,我需要在controller请求的时候new 一个StopWatch的id,然后在接下来的一系列调用中都使用这个id对应的计时器,最后销毁。如果几百万个并发量,那么就会产生几百万个类实例。懵逼。

    最终选择

    通过一个实例来保存一次请求状态太消耗性能,而如果不通过一个类实例保存状态就无法汇总所有的时间。所以这个方案不合适。那么,剩下日志和传入数据库。当下就先记录日志好了。记录想要记录的节点的时间:

     View Code

    压力测试

    1.建立test case

    首先,测试controller:创建com.test.spring.aop.web.UserControllerTest

     View Code

    不知道为啥,我的new Thread并没有并发执行,而是按顺序执行的。也就是说,虽然我new 10 个Thread,但还是一个接一个的运行。

    改进
    于是,我想采用线程池:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Test
       public void testUsersConcurrent() throws  Exception{
     
           ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));
           //不能超过15
           for (int i = 0; i < 15; i++) {
               executor.execute(()->{
                   try {
                       mockMvc.perform(MockMvcRequestBuilders.get("/user/all")).andExpect(MockMvcResultMatchers.status().isOk());
                   catch (Exception e) {
                       e.printStackTrace();
                   }
               });
           }
     
           //等待其他线程执行,方便查看控制台打印结果
           Thread.sleep(100000);
       }

      

    这下就可以看到controller是多线程并发的了,因为瞬间就打印了10条controller访问日志,正好是线程池的最大容量:

     View Code

    2.采用Apache 的ab进行并发访问

    2.1下载Apache

    参考:使用Apache Server 的ab进行web请求压力测试

    2.2测试

     View Code

    可以看到服务端是多线程的:

     View Code

    项目源码:https://github.com/chenxing12/spring-aop

    So do it,and change it,no regret!
     
    分类: spring
  • 相关阅读:
    堆栈学习
    需要阅读的书籍
    Rust Book Lang Ch.19 Fully Qualified Syntax, Supertraits, Newtype Pattern, type aliases, never type, dynamic sized type
    Rust Lang Book Ch.19 Placeholder type, Default generic type parameter, operator overloading
    Rust Lang Book Ch.19 Unsafe
    Rust Lang Book Ch.18 Patterns and Matching
    Rust Lang Book Ch.17 OOP
    Rust Lang Book Ch.16 Concurrency
    Rust Lang Book Ch.15 Smart Pointers
    HDU3966-Aragorn's Story-树链剖分-点权
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5679674.html
Copyright © 2011-2022 走看看