httpservletrequest 设置请求头_大部分程序员不知道的 Servelt3 异步请求,原来这么简单?
前言
当一个 HTTP 请求到达 Tomcat,Tomcat 将会从线程池中取出线程,然后按照如下流程处理请求:
- 将请求信息解析为 HttpServletRequest
- 分发到具体 Servlet 处理相应的业务
- 通过 HttpServletResponse 将响应结果返回给等待客户端
整体流程如下所示:
这是我们日常最常用同步请求模型,所有动作都交给同一个 Tomcat 线程处理,所有动作处理完成,线程才会被释放回线程池。
想象一下如果业务需要较长时间处理,那么这个 Tomcat 线程其实一直在被占用,随着请求越来越多,可用 I/O 线程越来越少,直到被耗尽。这时后续请求只能等待空闲 Tomcat 线程,这将会加长了请求执行时间。
如果客户端不关心返回业务结果,这时我们可以自定义线程池,将请求任务提交给线程池,然后立刻返回。
也可以使用 Spring Async 任务,大家感兴趣可以自行查找一下资料
但是很多场景下,客户端需要处理返回结果,我们没办法使用上面的方案。在 Servlet2 时代,我们没办法优化上面的方案。
不过等到 Servlet3 ,引入异步 Servlet 新特性,可以完美解决上面的需求。
异步 Servlet 执行请求流程:
- 将请求信息解析为 HttpServletRequest
- 分发到具体 Servlet 处理,将业务提交给自定义业务线程池,请求立刻返回,Tomcat 线程立刻被释放
- 当业务线程将任务执行结束,将会将结果转交给 Tomcat 线程
- 通过 HttpServletResponse 将响应结果返回给等待客户端
引入异步 Servlet3 整体流程如下:
使用异步 Servelt,Tomcat 线程仅仅处理请求解析动作,所有耗时较长的业务操作全部交给业务线程池,所以相比同步请求, Tomcat 线程可以处理 更多请求。
虽然我们将业务处理交给业务线程池异步处理,但是对于客户端来讲,其还在同步等待响应结果。
可能有些同学会觉得异步请求将会获得更快响应时间,其实不是的,相反可能由于引入了更多线程,增加线程上下文切换时间。
虽然没有降低响应时间,但是通过请求异步化带来其他明显优点:
- 可以处理更高并发连接数,提高系统整体吞吐量
- 请求解析与业务处理完全分离,职责单一
- 自定义业务线程池,我们可以更容易对其监控,降级等处理
- 可以根据不同业务,自定义不同线程池,相互隔离,不用互相影响
所以具体使用过程,我们还需要进行的相应的压测,观察响应时间以及吞吐量等其他指标,综合选择。
异步 Servelt 使用方式
异步 Servelt 使用方式不是很难,阿粉总结就是下面三板斧:
- HttpServletRequest#startAsync 获取 AsyncContext 异步上下文对象
- 使用自定义的业务线程池处理业务逻辑
- 业务线程处理结束,通过 AsyncContext#complete 返回响应结果
下面的例子将会使用 SpringBoot ,Web 容器选择 Tomcat
示例代码如下:
ExecutorService executorService = Executors.newFixedThreadPool(10);@RequestMapping("/hello")public void hello(HttpServletRequest request) { AsyncContext asyncContext = request.startAsync(); // 超时时间 asyncContext.setTimeout(10000); executorService.submit(() -> { try { // 休眠 5s,模拟业务操作 TimeUnit.SECONDS.sleep(5); // 输出响应结果 asyncContext.getResponse().getWriter().println("hello world"); log.info("异步线程处理结束"); } catch (Exception e) { e.printStackTrace(); } finally { asyncContext.complete(); } }); log.info("servlet 线程处理结束");}
浏览器访问该请求将会同步等待 5s 得到输出响应,应用日志输出结果如下:
2020-03-24 07:27:08.997 INFO 79257 --- [nio-8087-exec-4] com.xxxx : servlet 线程处理结束2020-03-24 07:27:13.998 INFO 79257 --- [pool-1-thread-3] com.xxxx : 异步线程处理结束
这里我们需要注意设置合理的超时时间,防止客户端长时间等待。
SpringMVC
Servlet3 API ,无法使用 SpringMVC 为我们提供的特性,我们需要自己处理响应信息,处理方式相对繁琐。
SpringMVC 3.2 基于 Servelt3 引入异步请求处理方式,我们可以跟使用同步请求一样,方便使用异步请求。
SpringMVC 提供有两种异步方式,只要将 Controller 方法返回值修改下述类即可:
- DeferredResult
- Callable
DeferredResult
DeferredResult 是 SpringMVC 3.2 之后引入新的类,只要让请求方法返回DeferredResult,就可以快速使用异步请求,示例代码如下:
ExecutorService executorService = Executors.newFixedThreadPool(10);@RequestMapping("/hello_v1")public DeferredResult hello_v1() { // 设置超时时间 DeferredResult deferredResult = new DeferredResult<>(7000L); // 异步线程处理结束,将会执行该回调方法 deferredResult.onCompletion(() -> { log.info("异步线程处理结束"); }); // 如果异步线程执行时间超过设置超时时间,将会执行该回调方法 deferredResult.onTimeout(() -> { log.info("异步线程超时"); // 设置返回结果 deferredResult.setErrorResult("timeout error"); }); deferredResult.onError(throwable -> { log.error("异常", throwable); // 设置返回结果 deferredResult.setErrorResult("other error"); }); executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(5); deferredResult.setResult("hello_v1"); // 设置返回结果 } catch (Exception e) { e.printStackTrace(); // 若异步方法内部异常 deferredResult.setErrorResult("error"); } }); log.info("servlet 线程处理结束"); return deferredResult;}
创建 DeferredResult 实例时可以传入特定超时时间。另外我们可以设置默认超时时间:
# 异步请求超时时间spring.mvc.async.request-timeout=2000
如果异步程序执行完成,可以调用 DeferredResult#setResult返回响应结果。此时若有设置 DeferredResult#onCompletion 回调方法,将会触发该回调方法。
同时我们还可以设置超时回调方法 DeferredResult#onTimeout,一旦异步线程执行超时,将会触发该回调方法。
最后 DeferredResult 还提供其他异常的回调方法 onError,起初阿粉以为只要异步线程内发生异常,就会触发该回调方法。尝试在异步线程内抛出异常,但是无法成功触发。
后续阿粉查看这个方法的 doc,当 web 容器线程处理异步请求时发生异常,才能成功触发。
Callable
Spring 另外还提供一种异步请求使用方式,直接使用 JDK Callable。示例代码如下:
@RequestMapping("/hello_v2")public Callable hello_v2() { return new Callable() { @Override public String call() throws Exception { TimeUnit.SECONDS.sleep(5); log.info("异步方法结束"); return "hello_v2"; } };}
默认情况下,直接执行将会输出 WARN 日志:
这是因为默认情况使用 SimpleAsyncTaskExecutor 执行异步请求,每次调用执行都将会新建线程。由于这种方式不复用线程,生产不推荐使用这种方式,所以我们需要使用线程池代替。
我们可以使用如下方式自定义线程池:
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)public AsyncTaskExecutor executor() { ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setThreadNamePrefix("test-"); threadPoolTaskExecutor.setCorePoolSize(10); threadPoolTaskExecutor.setMaxPoolSize(20); return threadPoolTaskExecutor;}
注意 Bean 名称一定要是 applicationTaskExecutor,若不一致, Spring 将不会使用自定义线程池。
或者可以直接使用 SpringBoot 配置文件方式配置代替:
# 核心线程数spring.task.execution.pool.core-size=10# 最大线程数spring.task.execution.pool.max-size=20# 线程名前缀spring.task.execution.thread-name-prefix=test# 还有另外一些配置,读者们可以自行配置
这种方式异步请求的超时时间只能通过配置文件方式配置。
spring.mvc.async.request-timeout=10000
如果需要为单独请求的配置特定的超时时间,我们需要使用 WebAsyncTask 包装 Callable 。
@RequestMapping("/hello_v3")public WebAsyncTask hello_v3() { System.out.println("asdas"); Callable callable=new Callable() { @Override public String call() throws Exception { TimeUnit.SECONDS.sleep(5); log.info("异步方法结束"); return "hello_v3"; } }; // 单位 ms WebAsyncTask webAsyncTask=new WebAsyncTask<>(10000,callable); return webAsyncTask;}
总结
SpringMVC 两种异步请求方式,本质上就是帮我们包装 Servlet3 API ,让我们不用关心具体实现细节。虽然日常使用我们一般会选择使用 SpringMVC 两种异步请求方式,但是我们还是需要了解异步请求实际原理。所以大家如果在使用之前,可以先尝试使用 Servlet3 API 练习,后续再使用 SpringMVC。
httpservletrequest 设置请求头_大部分程序员不知道的 Servelt3 异步请求,原来这么简单?相关推荐
- 有哪些新手程序员不知道的小技巧?
提到新手程序员,大家想到的第一个词可能就是:刷题.尤其是通过LeetCode刷题,想必新手程序员们都经历过这一步,甚至不少人认为只要在LeetCode上刷的题目够多,就一定能够进阶为大神. 但是,不难 ...
- 99%程序员不知道的编程必备工具,人工智能助你编程更轻松
最近在浏览技术社区,发现了一款能节省开发时间的 IDE 插件.是一个利用人工智能技术帮助程序员提高编程效率,节省时间的工具,它的功能简直是为了我们程序员量身定做的,一下勾起了我的好奇心. 这款插件叫a ...
- 有哪些初学者程序员不知道的小技巧?
提到新手程序员,大家想到的第一个词可能就是--刷题.尤其是通过LeetCode刷题,想必新手程序员们都经历过这一步,甚至不少人认为只要在LeetCode上刷的题目够多,就一定能够进阶为大神. 但是,不 ...
- 前端新手程序员不知道的 20个小技巧
1.作为前端开发者,使用双显示器能大幅提高开发效率. 2.学编程最好的语言不是PHP,是English. 3.东西交付之前偷偷测试一遍. 4.问别人之前最好先自己百度,google一下,以免问出太低级 ...
- 新手程序员不知道的小技巧!
1.作为前端开发者,使用双显示器能大幅提高开发效率. 2.学编程最好的语言不是PHP,是English. 3.东西交付之前偷偷测试一遍. 4.问别人之前最好先自己百度,google一下,以免问出太低级 ...
- 师妹问我:有哪些新手程序员不知道的小技巧?
阅读本文大概需要5分钟. 一个师妹问:洋哥,我今年应届毕业,刚开始写代码,不知道有没有一些新手需要注意的地方. 给了师妹一些建议之后,感觉这是个好问题!不光是新手程序员,很多小技巧小秘密恐怕老手也未必 ...
- ajax jq 图片上传请求头_如何使用js或jQuery向Ajax请求添加自定义HTTP头?
下面是一个使用XHR 2的示例:function xhrToSend(){ // Attempt to creat the XHR2 object var xhr; try{ xhr = new XM ...
- 80%程序员不知道的职场秘诀,升职加薪不是梦
作者 | 梁唐 来源 | TechFlow(ID:techflow2019) 头图 | CSDN 下载自东方IC 在互联网行业,我们经常用一个标准去评价一个人,这个标准就是ownership.一个有 ...
- as3程序员不知道的as2和as3的不同点
最近,因为项目开发需要,得用as2实现一些较复杂的功能,发现as2的和as3的一些差异,总结如下: 1.as2没有as3中const关键字 2.as2没有as3中包的概念,包路径在类名处定义.类没有p ...
最新文章
- 24GHz和77GHz毫米波雷达技术细节
- 模型优化的风向标:偏差与方差
- 水平拉滑轮组计算机械效率的题,机械效率杠杆论文,关于中考物理机械效率计算题*相关参考文献资料-免费论文范文...
- 7、Power Query-合并查询
- 在线小词典(mysql扩展库操作)
- BBSSDK论坛移动化实现方案
- Win10 JAVA安装及环境搭建(windows jdk,windows java环境配置)
- 解决 service、killall 等命令找不到的问题
- 使用celery执行Django串行异步任务
- 2021“数维杯”国际大学生数学建模竞赛A题思路
- 锐起2540无盘教程
- 彻底卸载流氓软件如360等 1. 使用卸载软件(附下载链接) 2. 安全模式删除两种方法
- HBuilderX搭建Vue项目
- 【业界新闻】浪潮高端存储系统技术发展及展望(上篇)
- 实现回到顶部功能的三种方法
- git rebase
- 车载网络: 常见车载网络
- B2B、B2C、C2C、O2O分别是什么
- 【Redis Cluster集群】redis cluster 多mster写入,读写分离,高可用
- 分享10大自动化测试框架,你用过几个?