前言通过我之前的Tomcat系列文章 , 相信看我博客的同学对Tomcat应该有一个比较清晰的了解了 , 在前几篇博客我们讨论了Tomcat在SpringBoot框架中是如何启动的 , 讨论了Tomcat的内部组件是如何设计以及请求是如何流转的 , 那么我们这篇博客聊聊Tomcat的异步Servlet , Tomcat是如何实现异步Servlet的以及异步Servlet的使用场景 。
手撸一个异步的Servlet我们直接借助SpringBoot框架来实现一个Servlet,这里只展示Servlet代码:
@WebServlet(urlPatterns = "/async",asyncSupported = true)@Slf4jpublic class AsyncServlet extends HttpServlet { ExecutorService executorService =Executors.newSingleThreadExecutor(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //开启异步,获取异步上下文 final AsyncContext ctx = req.startAsync(); // 提交线程池异步执行 executorService.execute(new Runnable() { @Override public void run() { try { log.info("async Service 准备执行了"); //模拟耗时任务 Thread.sleep(10000L); ctx.getResponse().getWriter().print("async servlet"); log.info("async Service 执行了"); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } //最后执行完成后完成回调 。ctx.complete(); } }); }上面的代码实现了一个异步的Servlet,实现了doGet方法注意在SpringBoot中使用需要再启动类加上@ServletComponentScan注解来扫描Servlet 。既然代码写好了 , 我们来看看实际运行效果 。
文章插图
我们发送一个请求后 , 看到页面有响应 , 同时 , 看到请求时间花费了10.05s,那么我们这个Servlet算是能正常运行啦 。有同学肯定会问 , 这不是异步servlet吗?你的响应时间并没有加快 , 有什么用呢?对 , 我们的响应时间并不能加快 , 还是会取决于我们的业务逻辑 , 但是我们的异步servlet请求后 , 依赖于业务的异步执行 , 我们可以立即返回 , 也就是说 , Tomcat的线程可以立即回收 , 默认情况下 , Tomcat的核心线程是10 , 最大线程数是200,我们能及时回收线程 , 也就意味着我们能处理更多的请求 , 能够增加我们的吞吐量 , 这也是异步Servlet的主要作用 。
异步Servlet的内部原理了解完异步Servlet的作用后 , 我们来看看 , Tomcat是如何是先异步Servlet的 。其实上面的代码 , 主要核心逻辑就两部分 , final AsyncContext ctx = req.startAsync()和 ctx.complete()那我们来看看他们究竟做了什么?
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; }我们发现req.startAsync()只是保存了一个异步上下文 , 同时设置一些基础信息 , 比如Timeout,顺便提一下 , 这里设置的默认超时时间是30S , 如果你的异步处理逻辑超过30S,此时执行ctx.complete()就会抛出IllegalStateException 异常 。
我们来看看ctx.complete()的逻辑
public void complete() { if (log.isDebugEnabled()) { logDebug("complete "); } check(); request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null); }//类:AbstractProcessorpublic final void action(ActionCode actionCode, Object param) { case ASYNC_COMPLETE: { clearDispatches(); if (asyncStatemachine.asyncComplete()) { processSocketEvent(SocketEvent.OPEN_READ, true); } break; }} //类:AbstractProcessor protected void processSocketEvent(SocketEvent event, boolean dispatch) { SocketWrApperBase<?> socketWrapper = getSocketWrapper(); if (socketWrapper != null) { socketWrapper.processSocket(event, dispatch); } } //类:AbstractEndpointpublic boolean processSocket(SocketWrapperBase<S> socketWrapper, SocketEvent event, boolean dispatch) { //省略部分代码 SocketProcessorBase<S> sc = null; if (processorCache != null) { sc = processorCache.pop(); } if (sc == null) { sc = createSocketProcessor(socketWrapper, event); } else { sc.reset(socketWrapper, event); } Executor executor = getExecutor(); if (dispatch && executor != null) { executor.execute(sc); } else { sc.run(); }return true; }
推荐阅读
- 湖南安化黑茶文化节主题歌你来的正是时候面世
- MySQL报错找不到问题?可能是你的SQL用了关键字
- 金矿石为什么是黑色的 矿石金掉色吗
- PHP Web开源的文件管理器
- thinkphp如何防止sql注入xss攻击
- IndexedDB 是什么
- 优秀架构师修成之路
- 阿里资深架构师:同样是数据中台,为什么差距那么大?
- Uber Go语言编码规范
- 自旋锁、排队自旋锁、MCS锁、CLH锁