P2M1_Apache_Tomcat_Web应用服务器

132 阅读14分钟

文章内容输出来源:拉钩教育Java高薪训练营

Apache Tomcat Web应用服务器

Tomcat 系统架构与原理剖析

浏览器访问服务器的流程

http协议只是定义了数据的组织格式(通信格式),是一个应用层协议。数据传输依靠的是TCP/IP协议。

  1. 用户发起请求
  2. 浏览器发起TCP连接请求
  3. 服务器接收请求,并建立连接(socket处理)
  4. 浏览器生成HTTP格式的数据包
  5. 浏览器发送请求数据包
  6. 服务器解析HTTP格式的数据包
  7. 服务器执行请求
  8. 服务器生成HTTP格式的数据包
  9. 服务器发送响应数据包
  10. 浏览器解析HTTP格式的数据
  11. 浏览器呈现静态数据给用户

Tomcat 系统总体架构

  • Tomcat是
    • Http服务器(接收和处理Http请求)
    • Servlet容器(请求交给Servlet容器来处理:通过Servlet接口调用业务类)
  • Tomcat Servlet 容器处理流程
    1. Http服务器将请求信息封装成ServletRequest对象
    2. Servlet容器根据URL和Servlet的映射关系,找到对应的Servlet(定位)
    3. 如果Servlet还没有加载,使用反射机制创建,并调用init方法来完成初始化(加载)
    4. 调用Servlet的service方法来处理请求,处理结构用ServletResponse对象封装(调用)
    5. Http服务器把响应发送给客户端
  • Tomcat两个核心组件
    • 连接器(Connector)
      • 对外交流:处理Socket连接,负责网络字节流与Request/Response对象的转化
    • 容器(Container)
      • 对内处理:加载和管理Servlet,具体处理Request请求

Tomcat 连接器组件 Coyote

  • 功能/作用
    • Coyote封装了底层的网络通信(Socket请求及响应处理)
    • Coyote使容器组件与具体的请求协议(http,应用层)及IO操作方法(io模型,传输层)完全解耦
    • Coyote将Socket输入转换封装成Request对象,进一步交予容器;处理结束后,容器通过Coyote提供的Response对象将结果写入输出流
    • Coyote负责的是**具体协议(应用层)IO(传输层)**相关内容
  • Coyote 支持的IO模型与协议
    • 应用层
      • HTTP/1.1(默认)
      • AJP/1.3
      • HTTP/2
    • 传输层
      • BIO(8之前):同步阻塞IO
      • NIO:同步非阻塞IO(JDK7)(默认)
      • NIO2:异步IO
      • APR(需单独按照APR库)
  • Coyote 的内部组件及流程
    • EndPoint:实现传输层TCP/IP协议,完成Socket接收和发送处理
    • Processor:实现应用层Http协议处理,解析并封装字节流成Tomcat Request/Response
    • ProtocolHandler:以上两个组件的组合,实现针对具体协议的处理能力。提供了6个实现类。
      • AjpNioProtocol, AjpAprProtocol, AjpNio2Protocol
      • Http11NioProtocol, Http11Nio2Protocol, Http11AprProtocol
    • Adaptor:Request/Response <---> ServletRequest/ServletResponse(适配器模式)

Tomcat Servlet 容器 Catalina

  • Tomcat 模块分层结构图

    image-20201106224938772

    Tomcat本质就是一款Servlet容器,Catalina是Tomcat的核心。其他模块都是为Catalina提供支撑。

  • Tomcat 可以认为是一个Catalina实例,通过加载 server.xml完成其他实例的创建

    • 一个Server

      • 多个 Services

      • ...

      • 多个 Services

        • 多个Connector

        • ...

        • 多个Connector

          ​ +

        • 一个Container

  • Container 组件的具体结构

    • Engine : 引擎
      • Host:虚拟主机、站点
      • ...
      • Host
        • Context:Web应用程序
        • ...
        • Context
          • Wrapper : Servlet,最底层容器
          • ...
          • Wrapper

Tomcat 服务器核心配置详解

  • conf/server.xml

  • Server 标签:根元素,创建一个Server实例

    • Listener:定义监听器
    • GlobalNamingResources:定义服务器的全局JNDI资源
    • Service:定义服务,可以有多个
    <!--监听8005端口,执行关闭-->
    <Server port="8005" shutdown="SHUTDOWN">
        <!--以日志形式输出服务器、操作系统、JVM版本信息-->
        <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
        <!--Security listener. Documentation at /docs/config/listeners.html>-->
        <!--<Listener className="org.apache.catalina.security.SecurityListener" />-->
        <!--加载和销毁APR。如果找不到APR库,则会输出日志,不影响Tomcat启动-->
        <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
        <!--避免JRE内存泄漏-->
        <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
        <!--加载和销毁全局命名服务-->
        <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
        <!--避免ThreadLocal相关的内存泄漏-->
        <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
    
        <GlobalNamingResources>
        <!-- Editable user database that can also be used by
             UserDatabaseRealm to authenticate users-->
        <Resource name="UserDatabase" auth="Container"
                  type="org.apache.catalina.UserDatabase"
                  description="User database that can be updated and saved"
                  factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
                  pathname="conf/tomcat-users.xml" />
        </GlobalNamingResources>
    
        <!-- A "Service" is a collection of one or more "Connectors" that share
         a single "Container" Note:  A "Service" is not itself a "Container",
         so you may not define subcomponents such as "Valves" at this level.
         Documentation at /docs/config/service.html-->
        <Service name="Catalina">
          <!--省略,见后-->
        </Service>
    </Server>
    
  • Service 标签:创建Service实例,默认使用org.apache.catalina.core.StandardService

    • Listener:生命周期监听器

    • Executor:配置共享线程池

      • name:线程池名称,用于Connector中指定
      • namePrefix:所创建的每个线程的名称前缀,前缀+threadNumber
      • maxThreads:最大线程数
      • minSpareThreads:活跃/核心线程数,不会销毁,会一直存在
      • maxIdleTime:线程空闲时间,超过后销毁
      • maxQueueSize:在被执行前最大线程排队数
      • prestartminSpareThreds:启动线程池时是否启动minSpareThreads部分线程
      • threadPriority:线程池中线程优先级
      • className:线程池实现类,自定义需实现 org.apache.catalina.Executor 接口
      <!--The connectors can use a shared executor, you can define one or more named thread pools-->
      <Executor name="tomcatThreadPool"
          namePrefix="catalina-exec-"
          maxThreads="150"
          minSpareThreads="4"
          maxIdleTime="60000"
          maxQueueSize="Integer.MAX_VALUE"
          prestartminSpareThreds="false"
          threadPriority="5"
          className="org.apache.catalina.core.StandardThreadExecutor"/>
      
    • Connector:配置连接器,默认配置了两个,一个HTTP协议,一个AJP协议

      • port:端口号,创建服务器Socket并进行监听
      • protocol:当前支持的协议,默认为HTTP/1.1,自动切换机制选择JAVA NIO或者APR
      • executor:共享线程池的名称
      • maxThreads:配置内部线程池
      • minSpareThreads:配置内部线程池
      • acceptCount:能接受的最大请求数,和maxThreads保持一致
      • maxConnections:和maxThreads保持一致
      • connectionTimeout:接受连接后的等待超时时间,-1为不超时
      • compression:要不要启用压缩
      • compressionMinSize:超过多大需要进行压缩
      • disableUploadTimeout:上传超时时间
      • redirectPort:当前不支持SSL请求,需要SSL传输catalina自动将请求重定向到指定的端口
      • URIEncoding:指定编码URI的字符编码,tomcat8 默认UTF-8, 7默认ISO-8859-1
      <Connector port="8080"
          protocol="HTTP/1.1"
          executor="commonThreadPool"
          maxThreads="1000"
          minSpareThreads="100"
          acceptCount="1000"
          maxConnections="1000"
          connectionTimeout="20000"
          compression="on"
          compressionMinSize="2048"
          disableUploadTimeout="true"
          redirectPort="8443"
          URIEncoding="UTF-8" />
      
    • Engine:配置Servlet容器引擎

      • name:指定Engine的名称,默认为Catalina

      • defaultHost:默认使用的虚拟主机名称,当客户端请求指向的主机无效时使用,默认为localhost

      • Host标签:用于配置一个虚拟主机

      • Context标签:配置一个Web应用

        <Host name="www.abc.com" appBase="webapp" unpackWARs="true" autoDeploy="true">
            <!--docBase:Web应用目录或者War包的部署路径,可以是绝对路径,也可以是相对路径-->
            <!--path:Web应用的Context部分-->
            <Context docBase="/Users/carol/web_demo" path="/web3"></Context>
            <!--访问日志-->
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                   prefix="localhost_access_log" suffix=".txt"
                   pattern="%h %l %u %t &quot;%r&quot; %s %b" />
        </Host>
        

手写实现迷你版Tomcat

  • 需求分析

    • 名称:Minicat
    • 需求:
      • 提供服务,接受请求(Socket通信)
      • 请求信息封装成Request/Response对象
      • 客户端请求资源,静态(HTML)动态(Servlet)
      • 资源返回给客户端浏览器
  • v1.0:浏览器请求http://localhost:8080 返回一个固定的字符串到页面

    Bootstrap.java

    public class Bootstrap {
    
        // 定义socket监听端口号
        private int port = 8080;
    
        // 启动入口
        public static void main(String[] args) {
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        // Minicat启动需要初始化展开的操作
        public void start() throws IOException {
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("=========>Minicat start on port: " + port);
    
            while(true) {
                Socket socket = serverSocket.accept();
                // 接受请求
                OutputStream outputStream = socket.getOutputStream();
                String data = "Hello Minicat!";
                String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;
                outputStream.write(responseText.getBytes());
                socket.close();
            }
        }
    }
    

    HttpProtocolUtil.java

    public class HttpProtocolUtil {
        public static String getHttpHeader200(long contentLength) {
            return "HTTP/1.1 200 OK \n" +
                    "Content-Type: text/html \n" +
                    "Content-Length: " + contentLength + " \n" +
                    "\r\n";
        }
    
        public static String getHttpHeader404() {
            String str404 = "<h1>404 not found</h1>";
            return "HTTP/1.1 404 NOT Found \n" +
                    "Content-Type: text/html \n" +
                    "Content-Length: " + str404.getBytes().length + " \n" +
                    "\r\n" + str404;
        }
    }
    
  • v2.0:封装Request和Response对象,返回html静态资源文件

    Bootstrap.java

    public void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=========>Minicat start on port: " + port);
    
        while(true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
    
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());
    
            response.outputHtml(request.getUrl());
            socket.close();
        }
    }
    

    Request.java

    public class Request {
        // 请求方式GET/POST
        private String method;
        // 例如 /,/index.html
        private String url;
        // 输入流
        private InputStream inputStream;
    
        public Request() {
        }
    
        public Request(InputStream inputStream) throws IOException {
            this.inputStream = inputStream;
    
            int count = 0;
            while(count == 0) {
                count = inputStream.available();
            }
            byte[] bytes = new byte[count];
            inputStream.read(bytes);
    
            String inputStr = new String(bytes);
    
            String firstLine = inputStr.split("\\n")[0];
            String[] strings = firstLine.split(" ");
            this.method = strings[0];
            this.url = strings[1];
    
            System.out.println("=========>method: " + this.method);
            System.out.println("=========>url: " + this.url);
        }
    }
    

    Response.java

    public class Response {
        private OutputStream outputStream;
    
        public Response() {
        }
    
        public Response(OutputStream outputStream) {
            this.outputStream = outputStream;
        }
    
        // url->静态资源的绝对路径->读出静态资源文件->输出流输出
        public void outputHtml(String path) throws IOException {
            String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
            File file = new File(absoluteResourcePath);
            if (file.exists() && file.isFile()) {
                StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
            } else {
                output(HttpProtocolUtil.getHttpHeader404());
            }
        }
    
        public void output(String content) throws IOException {
            outputStream.write(content.getBytes());
        }
    }
    

    StaticResourceUtil.java

    public class StaticResourceUtil {
        // 获取静态资源文件的绝对路径
        public static String getAbsolutePath(String path) {
            String absolute = StaticResourceUtil.class.getResource("/").getPath();
            return absolute.replaceAll("\\\\", "/") + path;
        }
    
        public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
            int count = 0;
            while (count == 0) {
                count = inputStream.available();
            }
    
            int resourceSize = count;
            outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
    
            // 读取内容输出
            long written = 0;
    
            // 每次缓冲的长度
            int byteSize = 1024;
            byte[] bytes = new byte[byteSize];
    
            while(written < resourceSize){
                if (written + byteSize > resourceSize) {
                    // 剩余的文件长度
                    byteSize = (int) (resourceSize - written);
                    bytes = new byte[byteSize];
                }
    
                inputStream.read(bytes);
                outputStream.write(bytes);
    
                outputStream.flush();
                written += byteSize;
            }
        }
    }
    
  • v3.0:可以请求动态资源(Servlet)

    Bootstrap.java

    public void start() throws Exception {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=========>Minicat start on port: " + port);
    
        // 加载解析配置文件
        loadServlet();
    
        while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
    
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());
    
            if (!servletMap.containsKey(request.getUrl())) {
                response.outputHtml(request.getUrl());
            } else {
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request, response);
            }
            socket.close();
        }
    }
    
    private void loadServlet() {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (Element element : selectNodes) {
                Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletNameElement.getStringValue();
    
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();
    
                // 根据servletName找到url-pattern
                Element servletMapping =
                        (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='"
                                + servletName +
                                "']");
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (DocumentException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }
    

    Servlet.java

    public interface Servlet {
        void init() throws Exception;
    
        void destroy() throws Exception;
    
        void service(Request request, Response response) throws Exception;
    }
    

    HttpServlet.java

    public abstract class HttpServlet implements Servlet{
    
        public abstract void doGet(Request request, Response response);
        public abstract void doPost(Request request, Response response);
        @Override
        public void service(Request request, Response response) throws Exception {
            if ("GET".equalsIgnoreCase(request.getMethod())) {
                doGet(request, response);
            } else {
                doPost(request, response);
            }
        }
    }
    

    CarolServlet.java

    public class CarolServlet extends HttpServlet {
        @Override
        public void doGet(Request request, Response response) {
            String content = "<h1>CarolServlet doGet</h1>";
            try {
                response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void doPost(Request request, Response response) {
            String content = "<h1>CarolServlet doPost</h1>";
            try {
                response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 多线程改造(不使用线程池)

    Bootstrap.java

    while(true) {
        System.out.println("while true");
        Socket socket = serverSocket.accept();
        RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
        requestProcessor.run();
    }
    

    RequestProcessor.java

    public class RequestProcessor extends Thread {
    
        private Socket socket;
        private Map<String,HttpServlet> servletMap;
    
        public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
            this.socket = socket;
            this.servletMap = servletMap;
        }
    
        @Override
        public void run() {
            try{
                InputStream inputStream = socket.getInputStream();
    
                // 封装Request对象和Response对象
                Request request = new Request(inputStream);
                Response response = new Response(socket.getOutputStream());
    
                // 静态资源处理
                if(servletMap.get(request.getUrl()) == null) {
                    System.out.println("静态资源处理");
                    response.outputHtml(request.getUrl());
                }else{
                    // 动态资源servlet请求
                    System.out.println("动态资源servlet请求");
                    HttpServlet httpServlet = servletMap.get(request.getUrl());
                    httpServlet.service(request,response);
                }
                socket.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
  • 多线程改造(使用线程池)

    Bootstrap.java

    // 定义一个线程池
    int corePoolSize = 10;
    int maximumPoolSize = 50;
    long keepAliveTime = 100L;
    TimeUnit unit = TimeUnit.SECONDS;
    BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
    ThreadFactory threadFactory = Executors.defaultThreadFactory();
    RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
    
    ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
            workQueue, threadFactory, handler);
    
    while(true) {
        Socket socket = serverSocket.accept();
        RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
        executor.execute(requestProcessor);
    }
    

Tomcat 源码构建及核心流程源码解析

  • 源码构建

    • 解压 tar.gz 压缩包,得到⽬录 apache-tomcat-8.5.50-src

    • 进⼊ apache-tomcat-8.5.50-src ⽬录,创建⼀个pom.xml⽂件

    • 在 apache-tomcat-8.5.50-src ⽬录中创建 source ⽂件夹

    • 将 conf、webapps ⽬录移动到刚刚创建的 source ⽂件夹中

    • 给 tomcat 的源码程序启动类 Bootstrap 配置 VM 参数,因为 tomcat 源码运⾏也需要加载配置文件等

      -Dcatalina.home=C:/Users/Carol/Downloads/apache-tomcat-8.5.59-src/source
      -Dcatalina.base=C:/Users/Carol/Downloads/apache-tomcat-8.5.59-src/source
      -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
      -Djava.util.logging.config.file=C:/Users/Carol/Downloads/apache-tomcat-8.5.59-src/source/conf/logging.properties
      
    • 在tomcat的源码ContextConfig类中的configureStart⽅法中增加⼀⾏代码将 Jsp 引擎初始化

      protected synchronized void configureStart() {
          // 省略
          webConfig();
      
          // 初始化jsp引擎
          context.addServletContainerInitializer(new JasperInitializer(), null);
      
          // 省略
      }
      
  • Tomcat启动流程

    • BootStrap(init -> start 逐级初始化->逐级启动)
      • Catalina
        • Server
          • Service
            • Engine
              • Host
              • Context
            • Executor
            • Connector
              • Protocolhandler
    • 生命周期接口Lifecycle进行统一规范,各容器组件实现该接口

    image-20201108180542643

    • service处示例

      image-20201108182159482

    • ProtocolHandler 接口结构

      image-20201108180629717

  • Tomcat请求处理流程

    • Mapper组件

      • 完成url和host, context, wrapper等容器的映射
      • MapElement基类:子类MappedHost-(1:n)->MappedContext-(1:n)->MapperdWrapper
    • 请求处理流程示意图

      image-20201108200529219

Tomcat 类加载机制剖析

  • 类加载:字节码文件被加载到jvm内存
  • 类加载器:一个特殊类,jvm启动时先把类加载器读取到内存中,再通过类加载器去加载其他的类(jar包中,自己开发的.class等)

JVM的类加载机制

  • 类加载器体系:系统类加载器 -> 扩展类加载器 -> 引导类加载器

    类加载器作用
    引导启动类加载器
    BootstrapClassLoader
    c++编写,加载java核心库 java.*,比如rt.jar中的类,构造ExtClassLoader和AppClassLoader
    扩展类加载器
    ExtClassLoader
    java编写,加载扩展库JAVA_HOME/lib/ext目录下的jar中的类,如classpath中的jre, javax.*或者java.ext.dir指定位置中的类
    系统类加载器
    SystemClassLoader/AppClassLoader
    默认的类加载器,搜索环境变量classpath中指明的路径
    自定义类加载器加载指定路径的class文件

JVM双亲委派机制

  • 自定义类加载器的加载过程:
    • 自定义类加载器把加载请求层层传递给父加载器,自顶向下查找加载
    • 如果父类加载不带,则转交子类
    • 一直到底层都没有加载到,则跑出异常ClassNotFoundException
  • 作用
    • 防止重复加载
    • 保证核心.class不能被篡改

Tomcat的类加载机制

  • 没有严格的遵从双亲委派机制

    image-20201108213024635
    • 引导类和扩展类作用不变
    • 系统类加载器默认应该加载CLASSPATH下的类,但是tomcat并未启用。而是加载tomcat启动类,比如bootstrap.jar
    • Common加载器加载Tomcat & 应用通用的类,例如servlet-api.jar
    • Catalina ClassLoader加载服务器内部可见类,应用不能访问
    • Shared ClassLoader加载应用共享类,这些类服务器不会依赖
    • Webapp ClassLoader,每个应用都有一个,互不干扰,加载本应用程序/WEB-INF/classes和/WEB-INF/lib下的类
  • Tomcat 8.5 默认改变了严格的双亲委派机制

    • 首先从Bootstrap Classloader加载指定的类(rt.jar)
    • 未加载到,从/WEB-INF/classes加载
    • 未加载到,从/WEB-INF/lib/*.jar加载
    • 未加载到,则依次从System, Common, Shared加载(使用双亲委派机制)

Tomcat 对Https的支持及Tomcat性能优化策略

Tomcat对HTTPS的支持

HTTPS和HTTP的主要区别

  • HTTPS协议使用时需要电子商务认证授权机构(CA)申请SSL证书(秘钥信息)
  • HTTP默认使用8080端口,HTTPS默认使用8443
  • HTTPS是具有SSL加密的安全性传输协议,对数据的传输进行加密
  • HTTP的连接是无状态的,不安全的;HTTPS协议是由SSL+HTTP协议构成的可加密传输、身份认证的网络协议

HTTPS工作原理

传输数据之前进行一次握手,确定双方加密传输数据的密码信息

  1. --> 浏览器将自己支持的一套加密规则发送给网站
  2. <-- 网站从中选出一组加密算法,并将自己的身份信息以证书的形式发回给浏览器。证书中包含了网站地址、加密公钥以及颁发机构
  3. |-- 浏览器验证证书合法性
  4. |-- 证书受信,或用户接受了不受信的证书,浏览器会随机生成一串随机数密码,用证书中的公钥进行加密
  5. --> 使用公钥加密后的随机数密码加密握手信息,发送给网站
  6. --| 网站a.使用私钥解密出随机数密码; b. 使用密码解密握手信息; c. 使用密码再加密一段握手信息,发送给浏览器
  7. |-- 浏览器解密并计算握手信息的Hash, 如果与服务器端发来的Hash一致,握手结束
  8. 之后所有的通信数据由之前浏览器生成的随机密码并利用对称加密算法进行加密

Tomcat 对 HTTPS 的支持

  1. 使用JDK中的keytool工具生成免费的秘钥库文件(证书)

    keytool -genkey -alias carol -keyalg RSA -keystore carol.key
    
  2. 配置conf/server.xml

    <Connector SSLEnabled="true" maxThreads="150" port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" schema="https" secure="true">
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="/Users/yingdian/workspace/servers/apache-tomcat8.5.50/conf/lagou.keystore" certificateKeystorePassword="lagou123" type="RSA"/>
        </SSLHostConfig>
    </Connector>
    
  3. 使用https协议访问8443端口(https://localhost:8443)

Tomcat性能优化策略

  • 衡量系统性能的指标:

    • 响应时间:执行某个操作的耗时
    • 吞吐量:系统在给定时间内能够支持的事务数量,单位为TPS(Transactions Per Second 事务/秒)
  • Tomcat优化的方向

    • JVM虚拟机优化(优化内存模型)
    • Tomcat自身配置的优化(是否使用了共享线程池?IO模型?)

虚拟机运行优化(参数调整)

  1. Java虚拟机内存相关参数

    参数参数作用优化建议
    -Server启动Server,以服务器模式运行建议开启
    -Xms最小堆内存建议与-Xmx设置相同
    -Xmx最大堆内存建议设置为可用内存的80%
    -XX:MetaspaceSize元空间初始值
    -XX:MaxMetaspaceSize元空间最大值默认无限
    -XX:NewRatio年轻代和老年代大小比例,取值为整数,默认为2不需要修改
    -XX:SurvivorRatioEden区与Survivor区大小比例,取值为整数,默认为8不需要修改
    JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
    

    可以使用JDK提供的内存映射工具进行查看

    jhsdb jmap --heap --pid xxx
    
  2. 垃圾回收(GC)策略

  • 垃圾回收性能指标

    • 吞吐量:工作时间(GC以外时间)占总时间的百分比
    • 暂停时间:由垃圾回收导致的应用程序停止响应次数/时间
  • 垃圾收集器

    • 串行收集器(Serial Collector):单线程执行所有的垃圾回收工作,适用于单核CPU服务器
    • 并行收集器(Parallel Collector):又称吞吐量收集器,以并行的方式执行年轻代的垃圾回收,吞吐量提高
    • 并发收集器(Concurrent Collector):并发的方式执行大部分垃圾回收工作,缩短暂停时间。响应时间优先
    • CMS收集器(Concurrent Mark Sweep Collector):并发标记清除收集器,更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处理器资源的应用
    • G1收集器(Garbage-First Garbage Collector):大容量内存的多核服务器,满足暂停时间目标的同时,以最大可能实现高吞吐量(JDK 1.7之后)
  • jconsole.exe

    image-20201108224757025

  • 垃圾回收器参数

    参数描述
    -XX:+UseSerialGC启用串行收集器
    -XX:+UseParallelGC启用并行垃圾收集器,启用后,默认启用-XX:+UseParallelOldGC
    -XX:+UseParNewGC年轻代采用并行收集器,如果设置了-XX:+UseConcMarkSweepGC选项,自动启用
    -XX:ParallelGCThreads年轻代及老年代垃圾回收使用的线程数。默认依赖于JVM使用的CPU个数
    -XX:+UseConcMarkSweepGC(CMS)对于老年代,启用CMS。当并行收集器无法满足延迟需求时,推荐使用CMS或者G1。启用该选项后,-XX:+UseParNewGC自动启用
    -XX:+UseG1GC启用G1。服务器类型的收集器,用于多核、大内存的及其。保持高吞吐量的情况下,高效率满足GC暂停时间的目标。

Tomcat配置调优

  • 调整tomcat线程池(Executor)
  • 调整tomcat的连接器
    • maxConnections:最大连接数
    • maxThreads:最大线程数
    • acceptCount:最大排队等待数
  • 禁用AJP连接器
  • 调整IO模式
    • Tomcat8之前默认使用BIO(阻塞式IO),不适合高并发;Tomcat8以后默认NIO
    • 并发性能有较高要求或者出现瓶颈时,可以尝试APR模式
  • 动静分离
    • Nginx + Tomcat相结合的部署方式(Tomcat不擅长处理静态资源)