异步 Servlet:Callable 和 DeferredResult

背景

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

DeferredResult 和 Callable 都是为了异步生成返回值提供基本的支持。简单来说就是一个请求进来,如果你使用了 DeferredResult 或者 Callable,在没有得到返回数据之前,DispatcherServlet 和所有 Filter 就会退出 Servlet 容器线程并释放其资源,同时也允许容器去处理其它请求。但响应保持打开状态,一旦返回数据有了,这个 DispatcherServlet 就会被再次调用并且处理,以异步产生的方式,向请求端返回值。 这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量

Callable

Callable 的实现比较简单,call() 方法的返回值就是服务端返回给请求端的数据

@RestController
public class AsyncCallableController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final TaskService taskService;
    
    @Autowired
    public AsyncCallableController(TaskService taskService) {
        this.taskService = taskService;
    }
    
    @RequestMapping(value = "/callable", method = RequestMethod.GET, produces = "text/html")
    public Callable<String> executeSlowTask() {
        logger.info("Request received");
        Callable<String> callable = taskService::execute;
        logger.info("Servlet thread released");
        
        return callable;
    }
}

返回 Callable 意味着 Spring MVC 将调用在不同的线程中执行定义的任务。Spring 将使用 TaskExecutor 来管理线程。在等待完成的长期任务之前,servlet 线程将被释放

[nio-8080-exec-5] x.s.w.c.AsyncCallableController          : Request received
[nio-8080-exec-5] x.s.w.c.AsyncCallableController          : Servlet thread released
[      MvcAsync2] x.spring.web.service.TaskServiceImpl     : Slow task executed

返回 Callable 对象时,实际工作线程会在后台处理,Controller 无需等待工作线程处理完成,可以服务于其他请求。但 Spring 会在工作线程处理完毕后才返回客户端

执行流程:

  • 客户端请求服务
  • SpringMVC 调用 Controller,Controller 返回一个 Callback 对象
  • SpringMVC 调用 request.startAsync 并且将 Callback 提交到 TaskExecutor 中去执行
  • DispatcherServlet 以及 Filters 等从应用服务器线程中结束,但 Response 仍旧是打开状态,也就是说暂时还不返回给客户端
  • TaskExecutor 调用 Callback 返回一个结果,SpringMVC 将请求发送给应用服务器继续处理
  • DispatcherServlet 再次被调用并且继续处理 Callback 返回的对象,最终将其返回给客户端

DeferredResult

DeferredResult 的处理过程与 Callback 类似,不一样的地方在于它的结果不是DeferredResult 直接返回的,而是由其它线程通过同步的方式设置到该对象中

该类包含以下日常使用相关的特性:

  • 超时配置:通过构造函数可以传入超时时间,单位为毫秒;因为需要等待设置结果后才能继续处理并返回客户端,如果一直等待会导致客户端一直无响应,因此必须有相应的超时机制来避免这个问题;实际上就算不设置这个超时时间,应用服务器或者 Spring 也会有一些默认的超时机制来处理这个问题

  • 结果设置:它的结果存储在一个名称为 result 的属性中;可以通过调用 setResult 的方法来设置属性;由于这个 DeferredResult 天生就是使用在多线程环境中的,因此对这个 result 属性的读写是有加锁的

一旦启用了异步请求处理功能,控制器就可以将返回值包装在 DeferredResult,控制器可以从不同的线程异步产生返回值。优点就是可以实现两个完全不相干的线程间的通信

@RestController
public class AsyncDeferredController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final TaskService taskService;
    
    @Autowired
    public AsyncDeferredController(TaskService taskService) {
        this.taskService = taskService;
    }
    
    @RequestMapping(value = "/deferred", method = RequestMethod.GET, produces = "text/html")
    public DeferredResult<String> executeSlowTask() {
        logger.info("Request received");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        CompletableFuture.supplyAsync(taskService::execute)
            .whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
        logger.info("Servlet thread released");
        
        return deferredResult;
    }
}

和 Callable 不同的是这一次线程是由我们管理。创建一个线程并将结果 set 到 DeferredResult 是由我们自己来做的

[io-8080-exec-10] x.s.w.c.AsyncDeferredController          : Request received
[io-8080-exec-10] x.s.w.c.AsyncDeferredController          : Servlet thread released
[onPool-worker-1] x.spring.web.service.TaskServiceImpl     : Slow task executed 

执行流程:

  • 客户端请求服务
  • SpringMVC 调用 Controller,Controller 返回一个 DeferredResult 对象
  • SpringMVC 调用 request.startAsync
  • DispatcherServlet 以及 Filters 等从应用服务器线程中结束,但 Response 仍旧是打开状态,也就是说暂时还不返回给客户端
  • 某些其它线程将结果设置到 DeferredResult 中,SpringMVC 将请求发送给应用服务器继续处理
  • DispatcherServlet 再次被调用并且继续处理 DeferredResult 中的结果,最终将其返回给客户端

Callable 和 DeferredResult 区别

  1. DeferredResult 更灵活,可以主动 setResult 到 DeferredResult 中并返回,实现两个完全不相干的线程间的通信
  2. Callable 是由 SpringMVC 管理异步线程,而 DeferredResult 是自己创建线程处理结果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,394评论 1 92
  • 16. Web MVC 框架 16.1 Spring Web MVC 框架介绍 Spring Web 模型-视图-...
    此鱼不得水阅读 1,039评论 0 4
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 12,291评论 6 86
  • 说文说字 题目叫《说文说字》?为何不叫《说文解字》?《说文解字》是研究汉字的经典著作,是大师的干的活。解,为人解惑...
    风起龙飞阅读 373评论 3 10
  • 喜欢 坐火车 靠窗的座位 喜欢看外面的风景 不论美还是不美 有时候 什么也不去想 只...
    浅怀阅读 141评论 0 0