Redian新闻
>
Spring DeferredResult 是个什么玩意?

Spring DeferredResult 是个什么玩意?

科技

大家好,我是yes。

今天我们就来盘一下 Spring DeferredResult,这玩意使用起来是很简单的,回答的话可深可浅。

浅一点就是直接回答它的作用,深一点的话就是原理了,而这个原理其实还需要涉及到 tomcat (默认 tomcat 为 web 容器)。

我们先来看下 DeferredResult 的作用及使用方式。

DeferredResult 的用处

DeferredResult 其实是基于 Servlet 3.0 对异步请求的支持而来的,我们来看这样一个场景:

当前 controller 里面有个方法 A,其内部逻辑依赖 redis 里面的一个值,如果 redis 里面有值了,就可以获取返回,如果没值这时候没有东西可以返回,只能返回 null,而往 redis 塞入值依赖另一个后台线程。

正常的实现我们肯定可以想到轮询的方案,即浏览器不断轮询方法 A,直到有值才停止轮询,但是有时候过于频繁的轮询会给服务器产生压力。

而这时候 DeferredResult 就可以登场啦,从名字我们就可以知道:延期的结果

我们来简单的看下使用方式:

可以看到,只需要用 DeferredResult 包装本来需要返回的值,然后设置一个超时时间和超时兜底值即可,例子设置了 5s。

我们来实验一下,如果后台线程就睡了 100 ms ,未超过设置的超时时间,此时过了 100 ms 就立马返回值:

如果后台线程睡了 10000 ms ,那么超过设置的超时时间,此时等待了 5 秒后,返回的是:

可以看到,这功能符合我们的预期。

关于 DeferredResult  还有一个很重要的点:请求的处理线程(即 tomcat 线程池的线程)不会等到 DeferredResult#setResult() 被调用才释放,而是直接释放了

也就是说 tomcat 线程安排好 DeferredResult 的一些配置后,不会等逻辑处理完(DeferredResult#setResult()的调用或者超时)。

而是直接释放了,这样 tomcat 线程就被回收到线程池中了,可以响应其他请求,不会傻傻地阻塞等着 DeferredResult#setResult() 被调用或超时。

我们都知道 tomcat 的线程池大小是有限的,如果我们的一些业务逻辑处理慢的话,会渐渐地占满 tomcat 线程,这样就无法处理新的请求,所以一些处理缓慢的业务我们会放到业务线程池中处理,但单纯的放到业务线程池中处理的话,我们无法得知其什么时候处理完,也无法将处理完的结果和之前的请求匹配上,所以常做的方式就是轮询。

而 DeferredResult 的做法就类似仅把事情安排好,不会管事情做好没,tomcat 线程就释放走了,注意此时不会给请求方(如浏览器)任何响应,而是将请求存放在一边,咱先不管它,等后面有结果了再把之前的请求拿来,把值响应给请求方。

最后还有个兜底的处理,如果超时了还没把该返回的值塞进来,那么就响应请求方兜底的值。

我来画个图(注意这不是源码分析图,仅仅只是为了更好地理解 DeferredResult 的逻辑):

看到这里想必你应该明白 DeferredResult 的用法以及它的一些特性了吧?

接下来我们看下原理。

DeferredResult 原理分析

这个原理说来话长,真要完全理解的话需要同时懂得 Tomcat 的原理和 SpringMvc 的原理,为了避免过度展开偏离主题,本文主要针对 DeferredResult 的原理。

后面有时间再补充完整 Tomcat 和 SpringMvc 的原理,有需要的可以评论区留个言,多的话我立马安排。

我打算主要用文字来描述整体的过程,会跟一些源码(如果全上源码的话我怕会看晕了,毕竟调用链路还是有点长的)

我们做到理解就行,真对源码有兴趣的可以自行研究

开始分析

一个请求先要通过 tomcat 的调度,然后才会到我们的 Spring 中来,待我们在 Spring 中处理完业务逻辑后,tomcat 才会把相关的响应返回给客户端(如浏览器)。

所以想要完成 DeferredResult 的功能,需要 tomcat 的配合,它需要晓得这是一个异步请求,在结果还未设置且没超时之前,不能将响应返回给客户端,待 DeferredResult 塞入值之后,再将请求返回给客户端。

我再贴一下示例代码,我们基于这个代码分析:

如果你用浏览器请求调用这个方法,那么 tomcat 此时肯定不知道这是一个异步处理的请求,只会认为是正常请求进行处理。

所以正常的话是 tomcat 调用 SpringMvc 定义的DispatcherServlet#doDispatch来处理请求。

根据 url 找到 controller 处理的方法即上文的实例方法。正常处理的话,这个方法应该给浏览器直接返回我们 new 的那个 deferredResult,但显然刚才演示的结果并不是这样。

那中间发生了什么事呢?

这就涉及到 SpringMvc 的内容。当通过反射调用 controller 中的方法得到返回值的时候,需要根据返回值的类型调用不同的 returnValueHandlers 来处理。

先来解释下为什么需要 returnValueHandlers 来处理返回值。

你想想看,如果我们返回类型可能是视图名或者是被标注了 @RequestBody 的值,这两者的处理方式肯定不一样,所以需要对返回值处理下。

而Spring搞了个 returnValueHandler 是专门用来处理 DeferredResult  类型的返回值,即 DeferredResultMethodReturnValueHandler

这里就可以做一些操作了,可以看到支持处理的返回类型是 DeferredResult :

从这里我们可以得知,对于 DeferredResult  返回类型其实前半部的处理和正常的返回值没有区别,区别就在于对方法的返回值做了特殊处理

我简要地说下这个 returnValueHandler 触发的操作:

它调用了 tomcat 里面 Request#startAsync 方法,也传递了 timeout 的时间,这个操作是让 tomcat 明白当前请求是一个异步请求,这样 tomcat 就不会直接将 new 的那个 deferredResult 返回给客户端,也不会销毁当前的 request 和 response 。

而是会将处理这个请求的 Processor 暂时保存起来(简单的理解为每个请求都有对应的一个 processor,这是 tomcat 里面的概念),放到 waitingProcessors 里面。

然后 tomcat 线程池就溜了,处理完了,不管了。

此时上半部的准备工作就做完了,后面有两种处理路径:

  • 一种是 timeout 了
  • 一种是在超时前调用了 DeferredResult#setResult 成功返回。

我们分别来看看两种的不同:

请求超时了

从前面我们已经得知,异步请求已经被放到 waitingProcessors 里,且超时时间也已经设置上了,而 tomcat 会有一个线程,每隔 1 秒遍历 waitingProcessors 里面的 processor ,看看它们过期了没:

如果发现过期了,那么会重新往 tomcat 里面线程池里面投掷任务,但是这个任务不太一样,可以看到 SocketEvent 是 TIMEOUT

这样线程池跑到这个任务的时候就知道这个已经超时请求任务,此时就会将超时值塞入到请求中(具体是通过之前设置的 DeferredResult 相关的 interceptor 中的 handleTimeout 方法)

这个 setResultInteral 最终就会将值保存到请求中(实际上是请求管理的 asyncManager 里),并将该请求重新进入 DispatcherServlet#doDispatch() 中处理。

你看这个请求又走了一遍 doDispatch,所以 DeferredResult 类型的方法会走两遍 doDispatch 逻辑,第二次走进来的时候发现请求里面的 asyncManager 已经被放入值了。因为已经塞了值,所以后面处理的时候,就会走到这个 if 里面,这里面就会把原先 controller 里面方法的反射内容替换了,新建一个反射内容,这个新建的反射方法的调用是直接返回塞入的 result。

看到没,这就是所谓的移花接木,这样还是复用了正常的处理流程,只不过后面执行的时候获得的是被塞入的超时值,且由于是正常类型,那么该怎么处理就怎么处理,最终返回给了浏览器。

请求未超时,DeferredResult#setResult

其实,DeferredResult#setResult 和请求超时的处理逻辑是一模一样的,差别就是触发的来源不同

请求超时是 tomcat 线程池扫描到超时,然后通过 handleTimeout 调用 setResultInternal将超时默认值塞入后重新触发 DispatcherServlet#doDispatch()  处理。

而 DeferredResult#setResult 是我们应用线程主动塞入值:

实际上也是调用 setResultInternal ,然后重新触发 DispatcherServlet#doDispatch()  处理。

所以两者后面的处理逻辑一模一样!

补充

其实里面有很多源码和比较重要的点都没提及,主要是展开的话太多了,这里稍微带一下。

比如 WebAsyncManager,这玩意就如其名是一个异步的管理器,每个请求都会 new 一个与之配对,上面也有提到一点点。

正常请求都只会进入一次到 doDispatch 方法中,而异步的 DeferredResult 进入了两次,这就要保证第二次进入的时候跟第一次进入是同一个 WebAsyncManager,一些信息需要缓存对齐,并且里面还有异步处理的逻辑,比如设置值重新触发 doDispatch 等等。

还有 tomcat 的请求其实有个状态机在,这里就需要了解 tomcat 的相关概念。

例如开始异步处理的时候会调用:

这个 action 底层就会调用 asyncStateMachine 进行操作:

在这里插入图片描述

然后这种状态机最终是通知到 CoyoteAdapter 的,这也是 tomcat 里面的一个概念。

tomcat 的连接器是通过调用 CoyoteAdapter#service 来调用容器的,这里面有异步请求的处理


    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
            throws Exception 
{

        ...省略部分代码....

        try {
            // Parse and set Catalina and configuration specific
            // request parameters
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                // Calling the container 调用容器
                connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);
            }
            if (request.isAsync()) { //这里就是通过状态机判断的
                async = true;
                .....省略部分代码....
            } else {
      //如果不是异步就结束了
                request.finishRequest(); 
                response.finishResponse();
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            .... 省略部分代码....
            if (!async) {
                updateWrapperErrorCount(request, response);
                //不是异步就回收request和response了
                request.recycle();
                response.recycle();
            }
        }
    }

最后

大概了解到这个程度差不多了,如果想要深入了解还是需要掌握挺多内容的。

用简单的话来总结下 Spring DeferredResult :如果返回值类型是 DeferredResult  则表明其是异步请求,tomcat 线程不会等到应用程序处理完或者超时,而是会立即释放线程。

而这个未处理完的请求则会暂存,tomcat 知晓其为异步请求,也不会对客户端进行响应,直至 tomcat 线程扫描到请求超时或者应用线程将 result 塞入到 DeferredResult 中。

主要原理就是 Spring 有个 DeferredResultMethodReturnValueHandler,识别到返回值是 DeferredResult  类型的话,会将这个请求标记为异步请求(这是基于 tomcat 对 servlet 3.0 异步请求的支持),且暂存这个请求。

待 tomcat 每秒扫描等待的异步请求是否超时来触发是否返回默认值,或者应用线程手动塞值到 DeferredResult  触发返回,具体返回的逻辑其实是二次利用 Spring 的 DispatcherServlet#doDispatch,进行再次分发。

利用一个 WebAsyncManager 对象与请求进行绑定,进行异步操作的管理,如返回值的保存,上下文的保存等。

通过 WebAsyncManager  状态的不同 DispatcherServlet#doDispatch 处理逻辑有所不同,判断其实异步请求、判断其是否已经有异步返回值等。

如果有了返回值则通过新建一个反射内容替换之前 request 对应的 controller 的方法,通过移花接木的方式替换反射返回的结果。

好了,就这样差不多了。

前面我也提了,这其实涉及到 tomcat 的底层原理,然后还有 SpringMvc 的原理,说实话作为面试题不错,可以延伸挺多的~

好嘞,后面有时间再写写相关的,今天就说这么多了!

我是yes,从一点点到亿点点,我们下篇见~

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Learning Device Accused of Offering ‘Inappropriate’ Content英国秋招 | FTI Consulting开放2023伦敦Full Time岗位投递通道!Refining Success 重新定义成功高温干旱给商业带来的最大影响是...parch/swelter/torrid/suffocating·每日一词合辑Sixth Tone China Writing Contest Award Ceremony ScheduledIn a Rural Chinese County, Progress Is Measured by the WormDisabled Man in Chinese Countryside Inspires Disillusioned Youth【CSR 可以refer了】信用卡 Refer a Friend 福利介绍那些在非洲拍视频的中国人写在父亲节前Cities Offer Huge ‘Group Buying’ Discounts on Unsold Properties恭喜!UCL学员斩获Deloitte (UK) Consulting暑期实习Offer!How Accessibility Is Changing China’s Coffee CultureKTV Fined for Offering Songs by ‘Tainted Artist’ Kris WuOn the Operating Table, Supply Chain Issues Can Be Life or DeathThe Pet Industry Is Booming. So Are Its Horrific Breeding Mills.Tangshan Police Officer Dismissed After Female Diners AssaultedMore Air Conditioning Workers Died This Summer: Media Report走路也可以打太极英国秋招 | 精品咨询公司L.E.K. Consulting开放2023 Full Time!【$400 开卡奖励;小幅改版】AmEx Blue Cash Preferred (BCP) 信用卡参加食品银行Refurbished Samsung Galaxy S21 Ultra 5G 128GB (Black)日本尿素的故事美国“芯片法案”将获通过,这是个啥玩意?禁止大厂扩大对华先进制程投资,倒逼国产替代加速,谁最先崛起?[汽车] Sheer Driving Pleasure——BMW 5 Series GranTurismo 9周年记恭喜DBC职梦NYU本科学员成功拿下EY (US) 2023 Tech Consulting Offer!Chinese Experts Refute ‘Wrong’ Claims on Domestic COVID Vaccines学员Offer | Consult出身,也不妨碍拿$33w大包!Fatal Car Crash Sparks Safety Concerns Over Autonomous DrivingSummer (and Spring, and Fall) Heat Waves Are the New NormalRestaurants to Livestream From Kitchens to Ensure Food SafetyAmid Pro-Birth Blitz, China Pledges Benefits for Single MomsAfter a Blistering Summer, China Braces for a Frigid Winter[掌设] 真的Ultra吗?苍岭风Apple Watch Ultra开箱体验
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。