博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring boot 学习笔记(二)(servlet 3.0 异步请求)
阅读量:6651 次
发布时间:2019-06-25

本文共 4628 字,大约阅读时间需要 15 分钟。

hot3.png

我们可以尝试带着如下两个问题来学习servlet 3.0的异步请求模式。

1、是否真的可以提升服务器性能?

2、真实可应用场景?

一、同步请求模式

在了解servlet 3.0规范的异步请求之前,我们有必要先了解一下同步请求,然后我们再考虑,为什么需要异步请求,以及怎么做异步处理?下图是一个客户端发起的http的请求,往返WEB服务器的过程。

005224_mKmb_1589819.png

同步请求的原理:客户端发起一个http请求的时候,线程就会进入状态,直到接受到一个response对象或者请求超时状态。而http请求在经过dns服务器的域名解析,到nginx反向代理转发到我们的WEB服务器(servlet容器),WEB服务器会启动一个请求处理线程来处理请求,完成资源分配处理之后,线程起调后端的处理线程,同时WEB服务器的线程将会进入阻塞状态,直到后端的线程处理完毕,WEB服务器释放请求处理线程的资源,同时返回response对象,客户端接收到response对象,整个请求完成。

此时我们可以看到,假如在后端处理服务器中进行了大量的IO操作,数据库操作,或者跨网调用等等的问题,导致请求处理线程进入长时间的阻塞。因为WEB服务器的请求处理线程条个数是有限的,如果同时大量的请求阻塞在WEB服务器中,新的请求将会处于等待状态,甚至服务不可用,connection refused。

下边,解释一下tomcat 的两个相关的参数:

maxThreads:tomcat启动的最大线程数,即同时处理的线程个数,默认值为150

acceptCount:当tomcat起动的线程数达到最大时,接受排队的请求个数,值被我设置为30个。

它们是如何配合工作的呢?分如下三种情况:

A、接受一个请求,此时tomcat起动的线程数没有到达maxThreads,tomcat会启动一个线程来处理此请求。

B、接受一个请求,此时tomcat起动的线程数已经到达maxThreads,tomcat会把此请求放入等待队列,等待空闲线程。

C、接受一个请求,此时tomcat起动的线程数已经到达maxThreads,等待队列中的请求个数也达到了acceptCount,此时tomcat会直接拒绝此次请求,返回connection refused。

如此来说,我们的WEB服务器的maxThreads是有限值,即便有acceptCount做一个缓冲队列,请求可以进入队列中等待,那也有一个上限,并且在这最大的线程数中,有多少是分配给了请求处理线程?

二、异步请求模式

012057_p05I_1589819.png

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

而这个回调线程,我们也可以启用本地系统线程,并非一定要从WEB服务器线程池中取。

servlet3.0规范增加了对异步的支持,在servlet3.0规范之前,客户端请求到达servlet后,servlet通常会执行一些比较耗时的外部操作,比如数据库操作、I/O操作、跨网络调用等,往往会阻塞当前servlet线程,当前的线程是由servlet容器(tomcat)管理并分配的,容器线程池为请求分配的线程会持有一系列的servlet资源,因为该线程会调用一系列方法(大部分为tomcat内部方法),而方法内部可能会持有很多线程私有的对象,比如在一个有过滤器的web应用中,每个线程将持有私有的filterChain对象。如果阻塞时间过长,那么在这段时间内此线程无法被回收,为资源分配的内存一直被占用,无法被GC回收或者返回Object pool,且该线程也无法分配给其他客户端请求,在一定程度上会造成并发量的降低。

异步的目的:使后台操作异步,提早回收由容器管理的servlet线程。

我会用spring boot的例子来分析异步的整个过程,同时也会将原生servlet的异步实现打包上传上来(代码是山茶果先生写的,在下盗来使用)。

我们先看一下我们任务执行程序:

@Servicepublic interface TaskService {	Map
execute();}/*** 任务处理** */@Servicepublic class TaskServiceImpl implements TaskService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public Map
execute() { Map
map = new HashMap
(); List
tasks = new ArrayList
(); // 创建10个任务 for (int i = 0; i < 10; i++) { tasks.add("task:" + i); } // 循环处理10个任务,并借此打印当前的线程名称(通过线程名称可以看出线程是WEB服务器线程池取得,还是普通的线程) try { for (String task : tasks) { Thread.sleep(1 * 1000L); Thread current = Thread.currentThread(); logger.error(current.getName() + "执行进度:" + task + "/" + tasks.size()); } map.put("resut", "ok"); return map; } catch (InterruptedException e) { throw new RuntimeException(); } }}

OK,那么我们现在就通过创建spring boot程序,以及rest api来处理我们定下的任务。

@SpringBootApplication@EnableAsync // 支持异步请求public class TestAsyncApplication {	public static void main(String[] args) {		SpringApplication.run(TestAsyncApplication.class, args);	}}

spring提供了Callable<T> 接口来支持异步请求(从WEB服务器的线程取资源)

/** jdk8 的写法*/@RequestMapping("asyncCallable")public Callable
> asyncCallable() { logger.error("async start"); Callable
> callable = taskService::execute; logger.error("async end"); return callable;}/** 通用写法*/ @RequestMapping("asyncCallable") public Callable
> asyncCallable() { logger.error("async start"); Callable
> callable = new Callable
> (){ @Override public Map
call() throws Exception { // TODO Auto-generated method stub return taskService.execute(); } }; logger.error("async end"); return callable;}

spring提供了DeferredResult<T> 接口来支持异步异步请求(从操作系统中线程调度中取资源)

@RequestMapping("asyncDeferred")public DeferredResult
> deferredResult() { logger.error("async start"); DeferredResult
> deferredResult = new DeferredResult<>(); CompletableFuture.supplyAsync(taskService::execute) .whenCompleteAsync((result, throwable) -> deferredResult.setResult(result)); logger.error("async end"); return deferredResult;}

为了可以可以清楚的感受到线程处理的现象,我们可以分几步来操作。

1、不开启server.tomcat.max-threads这个参数,默认tomcat的最大线程数,默认应该是150个。

2、开启server.tomcat.max-threads=2,通过只有两个处理线程的情况可以清楚看到容器处理的情况。

这两步就不跑了,我们直接打开charles,在最大线程数为2的时候,并发10个请求的效果。

A、同步请求

201942_Fzp7_1589819.png

201734_IPQn_1589819.png

以上结果为我们发起十个并发请求的结果。我们可以看到当我们并发十个请求的时候,由于最大线程只有两个,也就是同时只有两个线程任务在处理,那么其他请求要么阻塞在tomcat的容器队列中,等待处理线程的资源,再多的请求有可能发生请求拒绝的情况了。

B、异步请求

201001_ptWf_1589819.png

 

 

 

 

 

 

以上结果为发起十个并发请求,我们可以看到线程请求线程几乎是同时处理完。也就说,在开启异步请求的模式下,请求到达WEB服务器,即可会分配请求处理线程进行处理,再另起线程去处理任务,而请求处理线程得到释放,就可以立刻接待新进来的请求。在任务处理完成之后,会勾起一个回调处理线程,将reponse的结果返回,终止一次http请求。

第二种异步接口可以自行执行测试,可以看到线程名称的不同。

三、小结:

异步请求的模式,最终结果集就是将线程处理的压力转交给其他线程,而解放了WEB服务器中的请求处理线程的压力,让他们可以更多的接受其他的http请求。

如果服务器的压力是CPU,I/O处理能力的话,那么异步请求的方法似乎并不能带来什么优势,相反会带来更多线程开启/释放的资源损耗。

到这里,对于servlet 3.0规范异步请求模式应该有所了解,那么回到我们最初的那个:

1、是否真的可以提升服务器性能?

2、真实可应用场景?

 

 

转载于:https://my.oschina.net/u/1589819/blog/979048

你可能感兴趣的文章
《系统与网络管理实践》(第三版)作者访谈
查看>>
除了输入法,移动端AI还有哪些想象空间?
查看>>
独家!阿里开源自用OpenJDK版本,Java社区迎来中国力量
查看>>
血淋淋的BUG:波音在软件开发上错在哪里?
查看>>
访谈:Kotlin在Pinterest的逆势生长
查看>>
云端能力知几许?12人众测华为云企业级Kubernetes集群实力
查看>>
JavaScript || this
查看>>
Safari浏览器的智能跟踪预防工作原理
查看>>
苹果iPhone X内置定制化神经引擎处理器
查看>>
Spring Web Services 3.0.4.RELEASE和2.4.3.RELEASE发布
查看>>
Microsoft Graph:连接每个应用都需要的基础数据
查看>>
Kotlin/Native应用程序开发指南
查看>>
Blazor将.NET带回到浏览器
查看>>
在首次发布三周之后,MLflow迎来了0.2版本
查看>>
全栈溯源、mAPM、金融性能、Oracle VS. MySQL:看APM技术专场有哪些干货
查看>>
矩阵:如何使用矩阵操作进行 PageRank 计算?
查看>>
InfoQ宣布成立CNUT容器技术俱乐部 欲连接中国容器社区
查看>>
Netflix如何设计一个能满足5倍增长量的时序数据存储新架构?
查看>>
微软正式发布PowerShell Core 6.0
查看>>
Nexus指南已经发布
查看>>