异步servlet的原理探究
异步servlet是servlet3.0开始支持的,对于单次访问来讲,同步的servlet相比异步的servlet在响应时长上并不会带来变化(这也是常见的误区之一),但对于高并发的服务而言异步servlet能增加服务端的吞吐量。本篇来从源码角度上来探究为何说异步servlet能增加服务端的吞吐量的?
首先来个简单的异步servlet的demo
@WebServlet(
name = "asynchelloServlet",
urlPatterns = {"/asynchello"},
asyncSupported = true
)
public class AsyncHelloServlet extends HttpServlet {
private static final ThreadPoolExecutor executor;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext ctx = req.startAsync();
executor.execute(() -> {
System.out.println("AsyncHello Start->" + LocalDateTime.now());
try {
PrintWriter writer = ctx.getResponse().getWriter();
writer.write("asyncHelloWorld");
} catch (IOException var2) {
var2.printStackTrace();
}
ctx.complete();
System.out.println("AsyncHello End->" + LocalDateTime.now());
});
}
static {
executor = new ThreadPoolExecutor(10, 20, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100));
}
}
上面的代码写异步servlet的写法最关键的就是
AsyncContext ctx = req.startAsync() ctx.complete()
我们先讲讲下当逻辑进入servlet之前,tomcat经历了哪些步骤:
tcp三次握手后 Acceptor线程处理 socket accept Acceptor线程处理 注册registered OP_READ到多路复用器 ClientPoller线程 监听多路复用器的事件(OP_READ)触发 从tomcat的work线程池取一个工作线程来处理socket[http-nio-8080-exec-xx],下面几个步骤也都是在work线程中进行处理的 因为是http协议所以用Http11Processor来解析协议 CoyoteAdapter来适配包装成Request和Response对象 开始走pipeline管道(Valve),最后一个invoke的是把我们的servlet对象包装的StandardWrapperValve管道
接下来就走到我们的servlet,由于是我们是异步的servlet,
1. req.startAsync()
@Override
public AsyncContext startAsync(ServletRequest request,
ServletResponse response) {
if (!isAsyncSupported()) {
IllegalStateException ise =
new IllegalStateException(sm.getString("request.asyncNotSupported"));
log.warn(sm.getString("coyoteRequest.noAsync",
StringUtils.join(getNonAsyncClassNames())), ise);
throw ise;
}
if (asyncContext == null) {
asyncContext = new AsyncContextImpl(this);
}
//修改状态机
asyncContext.setStarted(getContext(), request, response,
request==getRequest() && response==getResponse().getResponse());
asyncContext.setTimeout(getConnector().getAsyncTimeout());
return asyncContext;
}
从这里开始有2个线程我们要特别关注它们分别做了哪些事情:
tomcat的work线程 我们自定义的业务线程
当在tomcat的work线程中调用startAsync(),会创建了一个异步的上下文(AsyncContext),并且异步的上下文(AsyncContext)会设置这个状态机状态为 STARTING, 然后把这个异步上下文放到了我们的自定义线程池中去执行,
对于异步的servlet,有一个专门的状态机来控制:AsyncMachine,如下图
那状态机的扭转控制肯定也做针对异步做了什么特殊处理
这里是一个Socket状态的切换的处理逻辑,在异步servlet的时候是通过AsyncMachined的状态来连动Socket状态
如上图异步状态机的切换过程为:
DISPATCHED(初始)->STARTING->STARTED->COMPLETING
Socket的状态的切换为:LONG
对于tomcat的work线程而言,servlet调用就结束了! 正常来说,如果是同步servlet的话,request和response会在servlet执行完成后由tomcat释放掉!
异步的话 在这个时机request和response肯定不能释放掉,释放那不就没得完了!
虽然Request和Response没有释放,但是这根work线程回到tomcat的线程池中去了(非核心线程的话那就释放)。
2. ctx.complete()
回到我们的业务线程,处理完业务逻辑后,调用ctx.complete()
@Override
public void complete() {
if (log.isDebugEnabled()) {
logDebug("complete ");
}
check();
//更改异步状态机
request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null);
}
注意:COMPLETING是在我们的自定义的业务线程改变的!
修改状态会触发 新开一个tomcat工作线程 
异步状态状态切换:
COMPLETING->DISPATCHED
Socket状态切换为ASYNC_END
如下图,一次异步的完整过程如下图:
总结
研究了整个如何异步的过程,虽然这个状态机的切换挺绕的,会发现在异步servlet中,最大的改变是为了尽快的释放tomcat的work线程,让它有机会请求新accept过来的请求,接受更多的请求,当在自定义线程池中处理好业务逻辑后,在去启动新的tomcat的work线程来处理response,这样不就很好理解了为什么说异步servlet能增加服务端的吞吐量了对吧!
思考:
SpringBoot的@EnableAsync背后是异步servlet吗?
servlet 3.1的non-blocking I/O 解决了3.0的什么问题?
关注公众号一起学习

异步servlet的原理探究的更多相关文章
- 异步Servlet的理解与实践
AsyncContext理解 Servlet 3.0(JSR315)定义了Servlet/Filter的异步特性规范. 怎么理解"异步Servlet/Filter"及其使用情景? ...
- ThreadPoolExcutor 原理探究
概论 线程池(英语:thread pool):一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间 ...
- [原] KVM 虚拟化原理探究(1)— overview
KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...
- [原] KVM 虚拟化原理探究 —— 目录
KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...
- [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化
KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...
- [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化
KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...
- [原] KVM 虚拟化原理探究(4)— 内存虚拟化
KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...
- [原] KVM 虚拟化原理探究(3)— CPU 虚拟化
KVM 虚拟化原理探究(3)- CPU 虚拟化 标签(空格分隔): KVM [TOC] CPU 虚拟化简介 上一篇文章笼统的介绍了一个虚拟机的诞生过程,从demo中也可以看到,运行一个虚拟机再也不需要 ...
- [原] KVM 虚拟化原理探究(2)— QEMU启动过程
KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...
随机推荐
- SpringBoot开发十二-账号设置
需求介绍-账号设置 账号设置里面的上传头像(文件) 首先请求必须是一个 POST 请求,其次表单的属性 enctype = "multipart/form-data" 然后就是利用 ...
- 旧手机改造成web服务器并实现内网穿透
前几天由于gitee的审核引擎一通乱杀,使得gitee pages停止提供服务,心生更换服务器或者其他pages托管的想法,看了看价格感人的云服务器以及空空的钱包,这时,脑子有个奇怪的想法飘过,自己搞 ...
- Vue 插槽 slot的简单实用
- MySQL临时表与内存表
在MySQL中有三种虚拟表:临时表.内存表.视图.下面简单介绍一下临时表和内存表的使用. 1.临时表 MySQL临时表在我们需要保存一些临时数据时是非常有用的.临时表在MySQL 3.23版本中添加. ...
- 解析一个body片断
问题 假如你有一个HTML片断 (比如. 一个 div 包含一对 p 标签; 一个不完整的HTML文档) 想对它进行解析.这个HTML片断可以是用户提交的一条评论或在一个CMS页面中编辑body部分. ...
- python turtle的使用
turtle.pendown() # 放下画笔 turtle.penup() # 抬起画笔 turtle.pensize(int) # 设置画笔宽度,值为整数型 turtle.forward(f ...
- shiro(二)
public class AuthorizerTest { @Test public void testIsPermitted() { login("classpath:shiro-auth ...
- Fllink学习
1.Apache Flink 是一个面向分布式数据流处理和批量数据处理的开源计算平台,它能够基于同一个Flink运行时,提供支持流处理和批处理两种类型应用的功能. 现有的开源计算方案,会把流处理和批处 ...
- Java如何调用C语言程序,JNI技术
Java为什么要调用C语言编写的程序因为涉及操作系统底层的事件,Java是处理不了的,例如用户上传一个视频文件,需要后台给视频加上水印,或者后台分离视频流和音频流,这个事Java就做不了,只能交给C语 ...
- java《设计原则-里氏替换原则》
package dubbo.wangbiao.project.ThreadAndSocket.designprinciples.lishitihuanyuanze.k0; //长方形 public c ...