SpringMVC同步与异步请求

梦里梦外; 2022-02-19 15:21 459阅读 0赞

前言

  1. 使用异步servlet主要原因就是因为,在service方法中业务逻辑如果碰到io操作时间比较长的操作,这样这个service方法就会长时间占用tomcat容器线程池中的线程,这样是不利于其他请求的处理的,当线程池中的线程处理任务时,任务由于长时间io操作,肯定会阻塞线程处理其他任务,引入异步servlet的目的就是将容器线程池和业务线程池分离开。在处理大io的业务操作的时候,把这个操作移动到业务线程池中进行,释放容器线程,使得容器线程处理其他任务,在业务逻辑执行完毕之后,然后在通知tomcat容器线程池来继续后面的操作,这个操作应该是把处理结果commit到客户端或者是dispatch到其他servlet上。原始模型在处理业务逻辑的过程中会一直占有容器线程池,而异步servlet模型,可以看出在业务线程池处理的过程中,有一段时间容器线程池中的那个线程是空闲的,这种设计大大提高了容器的处理请求的能力。

什么是异步模式

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppblhZYW4_size_16_color_FFFFFF_t_70

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppblhZYW4_size_16_color_FFFFFF_t_70 1

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppblhZYW4_size_16_color_FFFFFF_t_70 2

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

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

Spring MVC的使用——DefferedResult

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

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

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

看这个例子:

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

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

  1. public interface LongTermTaskCallback {
  2. void callback(Object result);
  3. }
  4. public class LongTimeAsyncCallService {
  5. private final int CorePoolSize = 4;
  6. private final int NeedSeconds = 3;
  7. private Random random = new Random();
  8. private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(CorePoolSize);
  9. public static void makeRemoteCallAndUnknownWhenFinish(LongTermTaskCallback callback){
  10. System.out.println("完成此任务需要 : " + NeedSeconds + " 秒");
  11. scheduler.schedule(new Runnable() {
  12. @Override
  13. public void run() {
  14. callback.callback("长时间异步调用完成.");
  15. }
  16. }, "这是处理结果:)", TimeUnit.SECONDS);
  17. }
  18. }

输出的结果是:

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

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

Callable方式

  1. 返回DefferedResult<ModelAndView>并非唯一做法,还可以返回WebAsyncTask来实现“异步”,但略有不同,不同之处在于返回WebAsyncTask的话是不需要我们主动去调用Callback的,看例子:
  2. @RequestMapping(value="/longtimetask", method = RequestMethod.GET)
  3. public WebAsyncTask longTimeTask(){
  4. System.out.println("/longtimetask被调用 thread id is : " + Thread.currentThread().getId());
  5. Callable<ModelAndView> callable = new Callable<ModelAndView>() {
  6. @Override
  7. public ModelAndView call() throws Exception {
  8. Thread.sleep(3000); //假设是一些长时间任务
  9. ModelAndView mav = new ModelAndView("longtimetask");
  10. mav.addObject("result", "执行成功");
  11. System.out.println("执行成功 thread id is : " + Thread.currentThread().getId());
  12. return mav;
  13. }
  14. };
  15. return new WebAsyncTask(callable);
  16. }

很明显可知:请求进入时拦截了一次,将Callable返回结果时,将请求重新派发给容器时又拦截了一次,所以进了两次拦截;

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

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

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

Callable主要用来处理一些简单的逻辑,DeferredResult主要用于处理一些复杂逻辑

发表评论

表情:
评论列表 (有 0 条评论,459人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ajax同步请求异步请求

    在前后台请求数据交互的时候,我们经常用到ajax来进行数据的请求与返回,ajax请求的async字段是boolean类型,用来标识ajax请求是同步请求或者异步请求。async

    相关 springmvc异步处理请求

    有两种情况,第一种是业务逻辑复杂,但不需要业务逻辑的结果,第二种是需要返回业务逻辑的处理结果 第一种比较简单,利用多线程处理业务逻辑,或者利用spring中@Asyn注