高性能的关键:Spring MVC的异步模式

来源:互联网 发布:督促效率的软件 编辑:程序博客网 时间:2024/06/06 10:06

什么是异步模式

要知道什么是异步模式,就先要知道什么是同步模式,先看最典型的同步模式:

(图1)

浏览器发起请求,Web服务器开一个线程处理,处理完把处理结果返回浏览器。好像没什么好说的了,绝大多数Web服务器都如此般处理。现在想想如果处理的过程中需要调用后端的一个业务逻辑服务器,会是怎样呢?

(图2)

调就调吧,上图所示,请求处理线程会在Call了之后等待Return,自身处于阻塞状态。这也是绝大多数Web服务器的做法,一般来说这样做也够了,为啥?一来“长时间处理服务”调用通常不多,二来请求数其实也不多。要不是这样的话,这种模式会出现什么问题呢?——会出现的问题就是请求处理线程的短缺!因为请求处理线程的总数是有限的,如果类似的请求多了,所有的处理线程处于阻塞的状态,那新的请求也就无法处理了,也就所谓影响了服务器的吞吐能力。要更加好地发挥服务器的全部性能,就要使用异步,这也是标题上所说的“高性能的关键”。接下来我们来看看异步是怎么一回事:

(图3)

最大的不同在于请求处理线程对后台处理的调用使用了“invoke”的方式,就是说调了之后直接返回,而不等待,这样请求处理线程就“自由”了,它可以接着去处理别的请求,当后端处理完成后,会钩起一个回调处理线程来处理调用的结果,这个回调处理线程跟请求处理线程也许都是线程池中的某个线程,相互间可以完全没有关系,由这个回调处理线程向浏览器返回内容。这就是异步的过程。

带来的改进是显而易见的,请求处理线程不需要阻塞了,它的能力得到了更充分的使用,带来了服务器吞吐能力的提升。

Spring MVC的使用——DefferedResult

要使用Spring MVC的异步功能,你得先确保你用的是Servlet 3.0或以上的版本,Maven中如此配置:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>

我这里使用的Servlet版本是3.1.0,Spring MVC版本是4.2.3,建议使用最新的版本。

由于Spring MVC的良好封装,异步功能使用起来出奇的简单。传统的同步模式的Controller是返回ModelAndView,而异步模式则是返回DeferredResult<ModelAndView>

看这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value="/asynctask", method = RequestMethod.GET)
publicDeferredResult<ModelAndView> asyncTask(){
    DeferredResult<ModelAndView> deferredResult = newDeferredResult<ModelAndView>();
    System.out.println("/asynctask 调用!thread id is : " + Thread.currentThread().getId());
    longTimeAsyncCallService.makeRemoteCallAndUnknownWhenFinish(newLongTermTaskCallback() {
        @Override
        publicvoidcallback(Object result) {
            System.out.println("异步调用执行完成, thread id is : " + Thread.currentThread().getId());
            ModelAndView mav = newModelAndView("remotecalltask");
            mav.addObject("result", result);
            deferredResult.setResult(mav);
        }
    });
}

longTimeAsyncCallService是我写的一个模拟长时间异步调用的服务类,调用之,立即返回,当它处理完成时候,就钩起一个线程调用我们提供的回调函数,这跟“图3”描述的一样,它的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
publicinterfaceLongTermTaskCallback {
    voidcallback(Object result);
}
 
publicclassLongTimeAsyncCallService {
    privatefinalint CorePoolSize = 4;
    privatefinalint NeedSeconds = 3;
    privateRandom random = newRandom();
    privateScheduledExecutorService scheduler = Executors.newScheduledThreadPool(CorePoolSize);
    publicvoidmakeRemoteCallAndUnknownWhenFinish(LongTermTaskCallback callback){
        System.out.println("完成此任务需要 : " + NeedSeconds + " 秒");
        scheduler.schedule(newRunnable() {
            @Override
            publicvoidrun() {
                callback.callback("长时间异步调用完成.");
            }
        },"这是处理结果:)", TimeUnit.SECONDS);
    }
}

输出的结果是:

/asynctask 调用!thread id is : 46
完成此任务需要 : 3 秒
异步调用执行完成, thread id is : 47

由此可见返回结果的线程和请求处理线程不是同一线程。

还有个叫WebAsyncTask

返回DefferedResult<ModelAndView>并非唯一做法,还可以返回WebAsyncTask来实现“异步”,但略有不同,不同之处在于返回WebAsyncTask的话是不需要我们主动去调用Callback的,看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value="/longtimetask", method = RequestMethod.GET)
publicWebAsyncTask longTimeTask(){
    System.out.println("/longtimetask被调用 thread id is : " + Thread.currentThread().getId());
    Callable<ModelAndView> callable = newCallable<ModelAndView>() {
        publicModelAndView call() throwsException {
            Thread.sleep(3000);//假设是一些长时间任务
            ModelAndView mav = newModelAndView("longtimetask");
            mav.addObject("result","执行成功");
            System.out.println("执行成功 thread id is : " + Thread.currentThread().getId());
            returnmav;
        }
    };
    returnnewWebAsyncTask(callable);
}

其核心是一个Callable<ModelAndView>,事实上,直接返回Callable<ModelAndView>都是可以的,但我们这里包装了一层,以便做后面提到的“超时处理”。和前一个方案的差别在于这个Callable的call方法并不是我们直接调用的,而是在longTimeTask返回后,由Spring MVC用一个工作线程来调用,执行,打印出来的结果:

/longtimetask被调用 thread id is : 56
执行成功 thread id is : 57

可见确实由不同线程执行的,但这个WebAsyncTask可不太符合“图3”所描述的技术规格,它仅仅是简单地把请求处理线程的任务转交给另一工作线程而已。

处理超时

如果“长时间处理任务”一直没返回,那我们也不应该让客户端无限等下去啊,总归要弄个“超时”出来。如图:

(图4)

其实“超时处理线程”和“回调处理线程”可能都是线程池中的某个线程,我为了清晰点把它们分开画而已。增加这个超时处理在Spring MVC中非常简单,先拿WebAsyncTask那段代码来改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@RequestMapping(value="/longtimetask", method = RequestMethod.GET)
publicWebAsyncTask longTimeTask(){
    System.out.println("/longtimetask被调用 thread id is : " + Thread.currentThread().getId());
    Callable<ModelAndView> callable = newCallable<ModelAndView>() {
        publicModelAndView call() throwsException {
            Thread.sleep(3000);//假设是一些长时间任务
            ModelAndView mav = newModelAndView("longtimetask");
            mav.addObject("result","执行成功");
            System.out.println("执行成功 thread id is : " + Thread.currentThread().getId());
            returnmav;
        }
    };
 
    WebAsyncTask asyncTask = newWebAsyncTask(2000, callable);
    asyncTask.onTimeout(
            newCallable<ModelAndView>() {
                publicModelAndView call() throwsException {
                    ModelAndView mav = newModelAndView("longtimetask");
                    mav.addObject("result","执行超时");
                    System.out.println("执行超时 thread id is :" + Thread.currentThread().getId());
                    returnmav;
                }
            }
    );
    returnnewWebAsyncTask(3000, callable);
}

注意看红色字体部分代码,这就是前面提到的为什么Callable还要外包一层的缘故,给WebAsyncTask设置一个超时回调,即可实现超时处理,在这个例子中,正常处理需要3秒钟,而超时设置为2秒,所以肯定会出现超时,执行打印log如下:

/longtimetask被调用 thread id is : 59
执行超时 thread id is :61
执行成功 thread id is : 80

嗯?明明超时了,怎么还会“执行成功”呢?超时归超时,超时并不会打断正常执行流程,但注意,出现超时后我们给客户端返回了“超时”的结果,那接下来即便正常处理流程成功,客户端也收不到正常处理成功所产生的结果了,这带来的问题就是:客户端看到了“超时”,实际上操作到底有没有成功,客户端并不知道,但通常这也不是什么大问题,因为用户在浏览器上再刷新一下就好了。:D

好,再来看DefferedResult方式的超时处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@RequestMapping(value = "/asynctask", method = RequestMethod.GET)
    publicDeferredResult<ModelAndView> asyncTask() {
        DeferredResult<ModelAndView> deferredResult = newDeferredResult<ModelAndView>(2000L);
        System.out.println("/asynctask 调用!thread id is : " + Thread.currentThread().getId());
        longTimeAsyncCallService.makeRemoteCallAndUnknownWhenFinish(newLongTermTaskCallback() {
            @Override
            publicvoidcallback(Object result) {
                System.out.println("异步调用执行完成, thread id is : " + Thread.currentThread().getId());
                ModelAndView mav = newModelAndView("remotecalltask");
                mav.addObject("result", result);
                deferredResult.setResult(mav);
            }
        });
 
        deferredResult.onTimeout(newRunnable() {
            @Override
            publicvoidrun() {
                System.out.println("异步调用执行超时!thread id is : " + Thread.currentThread().getId());
                ModelAndView mav = newModelAndView("remotecalltask");
                mav.addObject("result","异步调用执行超时");
                deferredResult.setResult(mav);
            }
        });
 
        returndeferredResult;
    }

非常类似,对吧,我把超时设置为2秒,而正常处理需要3秒,一定会超时,执行结果如下:

/asynctask 调用!thread id is : 48
完成此任务需要 : 3 秒
异步调用执行超时!thread id is : 51
异步调用执行完成, thread id is : 49

完全在我们预料之中。

异常处理

貌似没什么差别,在Controller中的处理和之前同步模式的处理是一样一样的:

1
2
3
4
5
6
@ExceptionHandler(Exception.class)
    publicModelAndView handleAllException(Exception ex) {
        ModelAndView model = newModelAndView("error");
        model.addObject("result", ex.getMessage());
        returnmodel;
    }

还要再弄个全局的异常处理啥的,和过去的做法都一样,在此不表了。

原文出处: 蒋国纲

from: http://www.importnew.com/22368.html

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 气撑杆没力了怎么办 淋浴分水器坏了怎么办 花洒从侧面漏水怎么办 花洒接口处漏水怎么办 买到香港箭牌该怎么办 雨伞杆抽出来了怎么办 招行的young卡怎么办 高一数学50分怎么办 在无烟房抽烟了怎么办 喂奶粉的新生儿腹部很大怎么办 剑三身份证忘了怎么办 中药剂量吃大了怎么办 社保卡3年没激活怎么办 社保卡2年没激活怎么办 cad没保存关了怎么办 cad未响应没保存怎么办 办公椅扶手坏了怎么办 玩刺客信条卡bug怎么办 我玩刺客信条1卡怎么办 玩刺客信条3卡死怎么办 mac玩刺客信条卡怎么办 阴部长了个疙瘩怎么办 两个人觉得累了怎么办 朋友把我拉黑了怎么办 下雨了怎么办我好想你 雨停怎么办我好想你 下雨天怎么办我好想你 天谕账号忘记了怎么办 天谕账号被冻结怎么办 促黄体生成素低怎么办 地暖家里太干燥怎么办 剑灵摧毁了东西怎么办 想打嗝打不出来怎么办 孩子满100天要怎么办 宝宝吃奶粉过敏了怎么办 1岁宝宝不喝奶粉怎么办 母乳不够宝宝不喝奶粉怎么办 宝宝吃奶粉上火了怎么办 我小孩不喝奶粉怎么办 2岁宝宝不喝奶粉怎么办 婴儿吃奶粉上火了怎么办