Servlet只有同步模型是怎样的?

异步处理是Servlet3.0版本的重要功能之一,分析异步处理模型之前,先看看同步处理的过程是怎样的:

  • 客户端发起HTTP请求一个动态Servlet API,请求到达服务器端后经过静态服务器过滤后转交给Servlet容器,
  • 容器从主线程池获取一个线程,开始执行Servlet程序,执行结束后得到了完整的响应内容并将其返回给调用方
  • 然后将该线程还回线程池,整个过程都是由同一个主线程在执行。

上述模型存在什么问题呢?

  • 作为服务器要想提高并发性能,就只能通过提高线程池的最大线程数。即吞吐量瓶颈受线程池约束。
  • 更严重的问题是:Servlet执行期间始终占用了该线程池线程,假如某个高频且耗时的Servlet被请求,那就会占用大量甚至全部的线程池线程,进而导致没有线程来处理其它请求。

什么是异步处理模式?

相对于前面的同步处理,异步处理的核心本质就是提供了一个突破点:

让Servlet程序能够将这些慢操作分配给新线程来执行,同时尽快将该Servlet所占用的线程归还到容器线程池。

先来看看异步模式的Servlet程序是怎么样的:

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

@WebServlet(urlPatterns = "/asyncapi", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/html;charset=GBK");
        PrintWriter printWriter = response.getWriter();
        printWriter.println("<title>异步Servlet示例</title>");
        printWriter.println("进入Servlet的时间:" + new Date() + "<br/>");
        printWriter.println("执行Servlet的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>");

        // 创建AsyncContext,开始异步调用
        final AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(50 * 1000);// 设置异步调用的超时时长,限制HTTP响应的最大耗时
        asyncContext.start(
                new Runnable() {//创建新线程继续执行
                    public void run() {
                        try {
                            Thread.sleep(1 * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        printWriter.println("执行业务处理的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>");
                        ServletResponse response = asyncContext.getResponse();
                        /* ... print to the response ... */
                        printWriter.println("请求处理结束:" + new Date() + "<br/>");
                        asyncContext.complete();
                    }
                }
        );
        printWriter.println("退出Servlet的时间:" + new Date() + "<br/>");
    }
}

请求该API,根据返回值可以看到现象:

  • Servlet内部模拟耗时了1秒,postman监测到实际耗时也是1秒
  • 处理该次HTTP请求期间,共使用了两个线程
  • 进入和离开Servlet的时间一致,说明该线程占用很短暂
  • 执行业务处理的耗时操作占用了另外一个线程,且执行完成后才返回HTTP响应

至此,我们通过演示程序确实看到使用异步模式将Servet部分和耗时操作部分分配到了不同的线程执行。那么耗时操作用到的新线程来资源哪里?

异步处理模式的实现原理

根据文档描述,start()方法是启用新线程的关键,它是怎么实现的呢?扒拉源码看看吧

这需要我们查看Servlet容器的具体实现来验证,此处以Tomcat 9.0源码为例进行分析。

下载好源码后,首选找到接口AsyncContext及实现类AsyncContextImpl的源码:

上图看到tomcat的状态机进行调度后续的处理。

我们根据状态值检索到后续入口:

最终看到是通过executor来执行runnable程序,而executor即tomcat中可配置的连接池。

当然也不是只能使用start()来启用新线程,我们甚至可以手工new线程来执行耗时操作,演示如下:

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

@WebServlet(urlPatterns = "/asyncapi_newthread", asyncSupported = true)
public class AsyncServlet_NewThread extends HttpServlet {
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/html;charset=GBK");
        PrintWriter printWriter = response.getWriter();
        printWriter.println("<title>异步Servlet示例</title>");
        printWriter.println("进入Servlet的时间:" + new Date() + "<br/>");
        printWriter.println("执行Servlet的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>");

        // 创建AsyncContext,开始异步调用
        final AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(50 * 1000);// 设置异步调用的超时时长,限制HTTP响应的最大耗时

        new Thread(() -> {
            try {
                Thread.sleep(1 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            printWriter.println("执行业务处理的线程ID:" + Thread.currentThread().getId() + " Name=" + Thread.currentThread().getName() + "<br/>");
            ServletResponse aResponse = asyncContext.getResponse();
            /* ... print to the response ... */
            printWriter.println("请求处理结束:" + new Date() + "<br/>");
            asyncContext.complete();
        }).start();
        printWriter.println("退出Servlet的时间:" + new Date() + "<br/>");
    }
}

异步模式带来的好处是什么?

从编程方面提供了异步能力,在编程环节就可以提前将耗时较高的Servlet启用异步模式。

异步模式具备拆分线程池解耦的能力,配置异步模式从专有work线程池取线程,而不是与原Servlet线程池共用线程,可以显著提高Servlet容器的并发量,减小对其它Serlvet请求的影响。

同时要看到:异步模式只能说让Tomcat有机会接收更多请求,并不能提升特定服务的吞吐量。

参考资料:

Java EE7官方文档目录:https://docs.oracle.com/javaee/7/tutorial/index.html

Servlet的异步处理:https://docs.oracle.com/javaee/7/tutorial/servlets012.htm

Servlet的非阻塞IO:https://docs.oracle.com/javaee/7/tutorial/servlets013.htm

posted @ 2022-08-18 15:36 万德福儿 阅读(868) 评论(1) 推荐(0) 编辑
摘要: 近期在进行服务器TLS协议安全加固的过程中,发现了MySql.Data对TLS版本的限制。 具体操作是准备取消不安全协议TLS1.0和1.1的支持,于是通过工具将服务器的Server、Client协议的1.0、1.1均取消勾选,结果在测试服务器重启生效后发现程序启动失败了。 失败原因为:因为算法不同 阅读全文
posted @ 2022-07-22 16:00 万德福儿 阅读(383) 评论(0) 推荐(0) 编辑
摘要: 请求被中止: 未能创建 SSL/TLS 安全通道 阅读全文
posted @ 2022-07-07 23:28 万德福儿 阅读(4081) 评论(2) 推荐(10) 编辑
摘要: 随着各大浏览器支持和苹果的带头效应,HTTP2的应用会越来越广泛,但是规模庞大的.NET Framework应用却也不能为了连接HTTP2就升级到NET Core平台。通过本文提供的方案,可以最小成本的实现.NET Framework应用成功访问HTTP2站点。 阅读全文
posted @ 2022-07-07 15:18 万德福儿 阅读(1477) 评论(2) 推荐(7) 编辑
摘要: CLR的核心功能之一就是垃圾回收(garbage collection),关于GC的基本概念本文不在赘述。这里主要针对GC的两种工作模式展开讨论和研究。 Workstaction模式介绍 该模式设计的目的是用于客户端类的应用(Client),这类应用的部署特点是同一台机器会部署很多应用程序,并且这些 阅读全文
posted @ 2022-03-12 21:05 万德福儿 阅读(1501) 评论(0) 推荐(2) 编辑
摘要: 这是一个古老的话题。。。直入主题吧! 对winfrom的控件来说,多线程操作非常容易导致复杂且严重的bug,比如不同线程可能会因场景需要强制设置控件为不同的状态,进而引起并发、加锁、死锁、阻塞等问题。为了避免和解决上述可能出现的问题,微软要求必须是控件的创建线程才能操作控件资源,其它线程不允许直接操 阅读全文
posted @ 2022-03-11 18:09 万德福儿 阅读(1400) 评论(0) 推荐(1) 编辑
摘要: Kind是什么? k8s集群的组成比较复杂,如果纯手工部署的话易出错且时间成本高。而本文介绍的Kind工具,能够快速的建立起可用的k8s集群,降低初学者的学习门槛。 Kind是Kubernetes In Docker的缩写,顾名思义,看起来是把k8s放到docker的意思。没错,kind创建k8s集 阅读全文
posted @ 2022-03-06 21:32 万德福儿 阅读(4180) 评论(0) 推荐(1) 编辑
摘要: SOS是什么? 直观来说,sos就是一个程序集文件。这个程序集的作用就是让我们在使用windbg分析.net进程时,更加方便快捷。通过sos,我们可以清晰的查看CLR运行时的各类信息,辅助我们去理解托管内存的状态和含义。 这个程序集是随.NET Framework一起安装的,一般不需要单独安装。在我 阅读全文
posted @ 2022-02-22 21:57 万德福儿 阅读(2231) 评论(1) 推荐(4) 编辑
摘要: 背景说明 某天生产环境发生进程的活跃线程数过高的预警问题,且一天两个节点分别出现相同预警。此程序近一年没出现过此类预警,事出必有因,本文就记录下此次根因分析的过程。 监控看到的线程数变化情况: 初步的分析和发现 异常的进程在重启时分别保留了dump,这是进行下一步windbg分析的前提。 查看线程明 阅读全文
posted @ 2022-02-22 07:44 万德福儿 阅读(1520) 评论(4) 推荐(6) 编辑
摘要: 前几天在生产环境上redis创建连接方面的故障,分析过程中对ServiceStack.Redis的连接创建和连接池机制有了进一步了解。问题分析结束后,通过此文系统的将学习到的知识点整理出来。 从连接池获取RedisClient的流程 业务程序中通过PooledRedisClientManager对象 阅读全文
posted @ 2022-02-21 16:24 万德福儿 阅读(586) 评论(0) 推荐(1) 编辑
点击右上角即可分享
微信分享提示