SpringMVC异步处理太强大了,系统性能大幅提升 | SpringMVC系列第10篇

路人甲Java

共 5479字,需浏览 11分钟

 · 2021-07-28

大家好,我是【路人甲 Java】号主路人,本文如果对你有帮助,点个在看,顺便忙转发一下,非常需要大家的支持,对 java 有兴趣的朋友欢迎加我微信 itsoku 交流。

1、本篇内容

本文让大家掌握 springmvc 中异步处理请求,特别牛逼的一个功能,大家一定要掌握。

看本文之前,需要一些预备知识,大家先看下这篇文章:servlet3.0 中的异步处理

2、看段代码,分析问题

@ResponseBody
@RequestMapping("/async/m1.do")
public String m1() throws InterruptedException {
    long st = System.currentTimeMillis();
    System.out.println("主线程:" + Thread.currentThread() + "," + st + ",开始");
    //休眠3秒,模拟耗时的业务操作
    TimeUnit.SECONDS.sleep(3);
    long et = System.currentTimeMillis();
    System.out.println("主线程:" + Thread.currentThread() + "," + st + ",结束,耗时(ms):" + (et - st));
    return "ok";
}

这段代码很简单

  • 这段代码是 springmvc 提供的一个接口

  • 内部休眠了 3 秒钟,用来模拟耗时的操作

  • 方法内部有 2 条日志(日志中包含了当前线程、开始时间、结束时间、耗时)

浏览器中访问下这个接口,效果如下,可以看到接口耗时 3s 左右。

控制台输出

主线程:Thread[http-nio-8080-exec-1,5,main],1624889293055,开始
主线程:Thread[http-nio-8080-exec-1,5,main],1624889293055,结束,耗时(ms):3002

从输出中,我们可以看出,这个接口从开始到结束都是由 tomcat 中的线程来处理用户请求的,也就是说,3 秒这段时间内,tomcat 中的一个线程会被当前请求一直占用了则,tomcat 线程是有最大值的,默认情况下好像是 75,那么问题来了。

当 3 秒之内,来的请求数量超过了 tomcat 最大线程数的时候,其他请求就无法处理了,而此时 tomcat 中这些线程都处理 sleep 3s 的休眠状态,cpu 此时没活干,此时就会造成机器没活干,但是呢又不能处理新的请求,这就是坑啊,浪费资源,怎么办呢?

遇到这种场景的,也就是说接口内部比价耗时,但是又不能充分利用 cpu 的,我们可以采用异步的方式来处理请求,过程如下:

tomcat 线程,将请求转发给我们自定义的子线程去处理这个请求,然后 tomcat 就可以继续去接受新的请求了。

3、springmvc 中异步处理

主要有 3 个大的步骤。

step1:servlet 开启异步处理支持

web.xml 中开启 servlet 异步支持

step2:Filter 中添加异步支持

如果我们的异步请求需要经过 Filter 的,那么需要在 web.xml 对这个 Filter 添加异步支持.

step3:接口返回值为 DeferredResult

这个步骤中细节比较多,当需要异步响应请求的时候,返回值需要为 DeferredResult,具体参考下面案例代码,详细信息都在注释中了,大家注意看注释。

  • 第 1 步:创建 DeferredResult<返回值类型>(超时时间[毫秒],超时回调的代码)
  • 第 2 步:在子线程中异步处理业务,调用 DeferredResult 的 setResult 方法,设置最终返回到客户端的结果,此方法调用以后,客户端将接收到返回值,然后响应过程请求就结束了
  • 第 3 步:将 DefaultResult 作为方法返回值
/**
 * 使用springmvc的异步功能,业务处理放在异步线程中执行
 *
 * @param timeout 异步处理超时时间(毫秒)
 * @return
 */

@ResponseBody
@RequestMapping("/async/m2/{timeout}.do")
public DeferredResult m2(@PathVariable("timeout") long timeout) {
    long st = System.currentTimeMillis();
    System.out.println("主线程:" + Thread.currentThread() + "," + st + ",开始");
    /**
     * 1、创建DeferredResult<返回值类型>(超时时间[毫秒],超时回调的代码)
     */

    DeferredResult result = new DeferredResult(timeout, () -> {
        System.out.println("超时了");
        return "timeout";
    });
    //2、异步处理业务,
    new Thread(() -> {
        //开启一个异步线程,在异步线程中进行业务处理操作
        try {
            TimeUnit.SECONDS.sleep(3);
            //3、调用DeferredResult的setResult方法,设置最终返回到客户端的结果,此方法调用以后,客户端将接收到返回值
            result.setResult("ok");
        } catch (InterruptedException e) {
            result.setResult("发生异常了:" + e.getMessage());
        }
    }).start();
    long et = System.currentTimeMillis();
    System.out.println("主线程:" + Thread.currentThread() + "," + st + ",结束,耗时(ms):" + (et - st));
    //3、将DefaultResult作为方法返回值
    return result;
}

上面的 m2 方法个 timeout 参数,调用者通过这个参数来指定接口的超时时间,未超时的情况下,也就是说 timeout 大于 3 秒的时候,此时会输出 ok,否则将出现超时,此时会将 DeferredResult 构造器第 2 个参数的执行结果作为最终的响应结果,即会向客户端输出 timeout。

使用建议:案例开启了一个新的子线程来执行业务操作,生产环境中,建议大家采用线程池的方式,效率更高。

下面我们来通过 2 个 case 来模拟下这个接口超时和正常的结果。

4、模拟非超时请求

当 timeout 大于 3 秒时,才不会出现超时,此时我们传递 4000 毫秒来试试

控制台输出如下,可以看到主线程瞬间就结束了。

主线程:Thread[http-nio-8080-exec-6,5,main],1624891886020,开始
主线程:Thread[http-nio-8080-exec-6,5,main],1624891886020,结束,耗时(ms):0

5、模拟超时请求

当 timeout 小于 3 秒会出现超时,此时我们传递 1000 毫秒来试试

控制台输出如下,输出了超时信息,且通过前两行输出看出主线程瞬间就结束了,不会被请求阻塞。

主线程:Thread[http-nio-8080-exec-1,5,main],1624892109695,开始
主线程:Thread[http-nio-8080-exec-1,5,main],1624892109695,结束,耗时(ms):0
超时了

6、总结

当接口中有大量的耗时的操作,且这些耗时的操作让线程处于等待状态时,此时为了提升系统的性能,可以将接口调整为异步处理的方式。

7、案例代码

git地址:https://gitee.com/javacode2018/springmvc-series

8、SpringMVC 系列

  1. SpringMVC 系列第 1 篇:helloword
  2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping
  3. SpringMVC 系列第 3 篇:异常高效的一款接口测试利器
  4. SpringMVC 系列第 4 篇:controller 常见的接收参数的方式
  5. SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的
  6. SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?

9、更多好文章

  1. Spring 高手系列(共 56 篇)
  2. Java 高并发系列(共 34 篇)
  3. MySql 高手系列(共 27 篇)
  4. Maven 高手系列(共 10 篇)
  5. Mybatis 系列(共 12 篇)
  6. 聊聊 db 和缓存一致性常见的实现方式
  7. 接口幂等性这么重要,它是什么?怎么实现?
  8. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

10、推荐一个高质量的公众号

大家平时在学习技术的过程中,苦于找不到高质量的学习资料的,可以关注一下【Java 充电社】,这个号专注于为大家提供高质量的学习资源,已发布了大量高质量的学习视频、及资源,大家可以关注下。

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报