嵌入式容器的配置与应用


嵌⼊式容器的配置与应⽤

1.嵌⼊式容器的运⾏参数配置

在 Spring Boot 项⽬中,可以⽀持 Tomcat、Jetty、Undertow 的 Web 应⽤服务容器。 当我们添加了 spring-boot-starter-web 依赖后,默认会使⽤ Tomcat 作为嵌⼊式 Web 容器,不需要我们 单独部署,将 Web 应⽤打成 jar 包即可运⾏。

调整 SpringBoot 应⽤容器的参数两种配置⽅法

  • 修改配置⽂件(简单)
  • ⾃定义配置类 (专业调优)

⼀、配置⽂件⽅式

在 application.properties / application.yml 可以配置 Web 容器运⾏所需要的属性,可以通过该链接在官 ⽅⽹站查看关于 server 的所有配置项:[server-properties](Common Application Properties (spring.io))

  • server.xx 开头的是所有 servlet 容器通⽤的配置
  • server.tomcat.xx 开头的是 tomcat 容器特有的配置参数参数
  • server.jetty.xx 开头的是 Jetty 容器特有的配置参数参数
  • server.undertow.xx 开头的是 undertow 容器特有的配置参数参数

1.1 常⽤配置参数

参数 默认值 说明
server.port 8080 配置 Web 容器的端⼝号
server.servlet.session.time out 30m(30 分钟) session 失效时间。如果不写单位则默认单位是秒。 (注意:由于 Tomcat 中配置 session 过期时间是以 分钟为单位,如果这⾥设置是秒的话,那么会⾃动 转换为⼀个不超过所配置秒数的最⼤分钟数。⽐如 配置了 119 秒(1 分 59 秒),那么实际 session 过期时间 是 1 分钟)
server.servlet.contextpath / URL 访问路径的基础路径
server.tomcat.uriencoding UTF-8 配置 Tomcat 请求编码
server.tomcat.basedir 配置 Tomcat 运⾏⽇志和临时⽂件的⽬录。若不配 置,则默认使⽤系统的临时⽬录

1.2 tomcat 性能优化核⼼参数

tomcat 连接器⼯作原理图:

  • 在 Acceptor 之前维护⼀个请求接收队列,该队列的最⼤⻓度即:tomcat 可以接受的最⼤请求连接 数:server.tomcat.max-connections
  • Acceptor 监听连接请求,并⽣成⼀个 SocketProcessor 任务提交到线程池去处理
  • 当线程池⾥⾯的所有线程都被占⽤,新建的 SocketProcessor 任务被放⼊等待队列,即: server.tomcat.accept-count
  • 线程池的 server.tomcat.threads.max 决定了 tomcat 的极限 SocketProcessor 任务处理能⼒。不是越⼤越好,线程越多耗费的资源也越多
  • 线程池的 server.tomcat.threads.min-spare 在应⽤空闲时,保留⼀定的线程数在线程池内。避免请 求到来后,临时创建线程浪费时间
参数 默认值 说明
server.tomcat.maxconnections 8192 接受的最⼤请求连接数
server.tomcat.accept-count 100 当所有的线程都被占⽤,被放⼊请求队列等 待的最⼤的请求连接数量
server.tomcat.threads.max 200 最⼤的⼯作线程池数量
server.tomcat.threads.minspare 10 最⼩的⼯作线程池数量

⼆、⾃定义配置类⽅式

步骤:

  1. 建⽴⼀个配置类,加上@Configuration 注解

  2. 添加定制器 ConfigurableServletWebServerFactory

  3. 将定制器返回

@Configuration
public class TomcatCustomizer {
    @Bean
    public ConfigurableServletWebServerFactory configurableServletWebServerFactory(){
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addConnectorCustomizers(new MyTomcatConnectionCustomizer());
        return factory;
   }
    static class MyTomcatConnectionCustomizer implements TomcatConnectorCustomizer {
        public MyTomcatConnectionCustomizer() {
       }
        @Override
        public void customize(Connector connector) {
            connector.setPort(Integer.parseInt("8888"));
            connector.setProperty("maxConnections", "8192");
            connector.setProperty("acceptorThreadCount", "100");
            connector.setProperty("minSpareThreads", "10");
            connector.setProperty("maxThreads", "200");
       }
   }
}

这种⽅法可定制的内容更多,也更灵活。但需要深⼊理解 server 容器的底层实现原理及设计机制,也需要具备⼀定的 TomcatServletWebServerFactory 的 API 熟练度。

2.为 Web 容器配置 HTTPS

HTTPS 是 HTTP 协议的安全版本,旨在提供数据传输层安全性(TLS)。当你的应⽤不使⽤ HTTPS 的时候,浏览器地址栏就会出现⼀个不安全的提示。HTTPS 加密每个数据包以安全⽅式进⾏传输,并保护敏感数据免受窃听者或⿊客的攻击。

可以通过在 Web 应⽤程序上安装 SSL 证书来实现 HTTPS,互联⽹上受信任的证书通常是需要(CA)认证机构颁发的证书(通常是收费的)。⼀个标准的 SSL 证书,还是有点⼩贵的。国内的⼀些⼚商虽然可以提供免费的证书,但是都有⼀定的免费时效性限制。 如果是以学习为⽬的,我们也可以使⽤⾃签名证书,即:使⽤ Java Keytool ⽣成⾃签名证书。完全不需要购买 CA 机构认证的 SSL 证书。

⼀、如何⽣成⾃签名证书

管理员身份启动命令⾏,使⽤如下的 keytool 命令⽣成⾃签名证书:

keytool -genkeypair -alias selfsigned_localhost_sslserver -keyalg RSA -keysize 2048 -storetype
PKCS12 -keystore zimug-ssl-key.p12 -validity 3650

⾃签名证书受密码保护。命令回⻋之后,会提示输⼊密码(这个密码要记住,后⾯会⽤到)和其他详细信息。

完成上述步骤后,便会创建 PKS 密钥并将其存储在当前⽬录下。

命令参数说明:

  • -genkey:表示要创建⼀个新的密钥
  • -alias:表示 keystore 的别名
  • -keyalg:表示使⽤的加密算法是 RSA(⼀种⾮对称加密算法)
  • -keysize:表示密钥的⻓度
  • -keystore:表示⽣成的密钥存放位置
  • -validity:表示密钥的有效时间(单位为天)

二、将 SSL 应⽤于 Spring Boot 应⽤程序

  1. 复制 syhan-ssl-key,将其放在应⽤根⽬录下。
  2. 将 SSL 密钥信息添加到 application.yml 中。
server:
  port: 8888
  ssl:
    key-store: syhan-ssl-key.p12
    key-store-password: 123456
    key-store-type: PKCS12

三、测试

此时如果我们继续使⽤ http 协议去访问应⽤资源,会得到如下的响应信息:

Bad Request
This combination of host and port requires TLS.

使⽤ HTTPS 协议去访问应⽤资源,https://localhost:8888/hello。才会得到正确的结果。

四、将 HTTP 请求重定向为 HTTPS

⾸先配置两个服务端⼝,server.port 是我们真正的服务端⼝,即 HTTPS 服务端⼝。另外再定义⼀个 server.httpPort,当客户端访问该 HTTP 协议端⼝的时候,⾃动跳转到 HTTPS 服务端⼝。

server:
  port: 8888
  httpPort: 80

需要使⽤到上⼀节使⽤编码⽅式进⾏配置的⽅法。下⾯的配置类不⽤改。

@Configuration
public class TomcatCustomizer {

    @Value("${server.httpPort}")
    int httpPort;
    @Value("${server.port}")
    int httpsPort;


    @Bean
    public ConfigurableServletWebServerFactory configurableServletWebServerFactory(){
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(){
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint constraint = new SecurityConstraint();
                constraint.setUserConstraint("CONFIDENTIAL");

                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                constraint.addCollection(collection);
                context.addConstraint(constraint);
           }
       };;
        factory.addAdditionalTomcatConnectors(connector());
        //这⾥填充配置
        return factory;
   }

    public Connector connector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        //Connector监听的http的端⼝号
        connector.setPort(httpPort);
        connector.setSecure(false);
        //监听到http的端⼝号后转向到的https的端⼝号
        connector.setRedirectPort(httpsPort);
        return connector;
   }
}

这样当我们通过 HTTP 协议:http://localhost:80/hello的时候,浏览器访问地址就会⾃动的跳转到 HTTPS 连接器服务端⼝ https://localhost:8888/hello

3.切换到 jetty&undertow 容器

本节介绍将 SpringBoot 默认的 Tomcat 容器切换为 jetty 或者 undertow。虽然可以使⽤ jetty 或者 undertow 替换掉 tomcat,但是不建议这么做,但是 jetty 与 undertow 的 NIO 模型还是有必要学⼀下的, 这也是绝⼤部分 Web 应⽤中间件提供⽹络服务的 IO 模型。 可能在某些场景下,jetty 或者 undertow 的测试结果的某些指标会好于 tomcat。但是 tomcat 综合各⽅⾯ 条件来说,⽆论从性能、稳定性、资源利⽤率来说都是⽐较优秀的。

⼀、替换掉 tomcat

SpringBoot 默认是使⽤ tomcat 作为默认的应⽤容器。如果需要把 tomcat 替换为 jetty 或者 undertow,需 要先把 tomcat 相关的 jar 包排除出去。如下代码所示

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
  </exclusions>
</dependency>

如果使⽤ Jetty 容器,那么添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

如果使⽤ Undertow 容器,那么添加

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

如果不做特殊的调优配置,全部使⽤默认值的话,我们的替换⼯作就已经完成了。

⼆、Reactor NIO 多线程模型

  1. mainReactor 负责监听 server socket,⽤来处理新连接的建⽴,将建⽴的 socketChannel 指定注册给 subReactor。
  2. subReactor 维护⾃⼰的 selector, 基于 mainReactor 注册的 socketChannel 多路分离 IO 读写事件,读写⽹络数据,对业务处理的功能,将其扔给 worker 线程池来完成实际的请求任务处理。

三、切换为 Jetty Server

常⽤ jetty 调优配置参数

参数 默认值 说明
server.jetty.threads.acceptors -1.0 acceptor 线程的数量,acceptor 是⽤于连接接收 的连接器。当设置成-1 的时候,会根据 CPU 的逻 辑核数/8 来决定,最⼤不能超过 4 个
server.jetty.threads.selectors -1.0 selector 线程的数量. 当设置成-1 的时候,根据 CPU 的逻辑核数/2,最少 1 个
server.jetty.threads.min 8 worker ⼯作线程池最⼩线程数量
server.jetty.threads.max 200 worker ⼯作线程池最⼤线程数量

四、切换到 undertow

下⽂配置中的 io-threads 可以认为是 acceptor 线程数,⽤来处理连接的建⽴。

worker-threads 就是⼯作线程池的线程数量,⽤来处理实际请求任务。

server:
 port: 8888
  # 下⾯是配置undertow作为服务器的参数
 undertow:
    # 设置IO线程数, 它主要执⾏⾮阻塞的任务,它们会负责多个连接, 默认设置每个CPU核⼼⼀
个线程
   io-threads: 4
    # ⼯作任务线程池,默认为io-threads的8倍
   worker-threads: 32

4.打 war 包部署到外置 tomcat 容器

⼀、修改打包⽅式

<packaging>war</packaging>

⼆、 排除内置 tomcat 的依赖

使⽤外置的 tomcat,要将内置的嵌⼊式 tomcat 的相关 jar 排除。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

三、新增加⼀个类继承 SpringBootServletInitializer 实现

configure:

SpringBootServletInitializer 源码注释:

Note that a WebApplicationInitializer is only needed if you are building a war file and deploying it. If you prefer to run an embedded web server then you won’t need this at all.

如果你正在构建 WAR ⽂件并部署,则需要 WebApplicationInitializer。如果你喜欢运⾏⼀个嵌⼊式 Web 服务器,那么不需要这个。

public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        //此处的Application.class为带有@SpringBootApplication注解的启动类
        return builder.sources(BootLaunchApplication.class);
   }
}

注意事项:

使⽤外部 Tomcat 部署访问的时候,application.properties(或者 application.yml)中的如下配置将失效, 请使⽤外置的 tomcat 的端⼝,tomcat 的 webapps 下项⽬名进⾏访问。

server.port= server.servlet.context-path=

四、build 要有 finalName 标签

pom.xml 中的构建 build 代码段,要有应⽤最终构建打包的名称。

<finalName>boot-launch</finalName>

五、打包与运⾏

war ⽅式打包,打包结果将存储在项⽬的 target ⽬录下⾯。

mvn clean package -Dmaven.test.skip=true

然后将 war 包 copy 到外置 Tomcat webapps ⽬录⾥。 在外置 tomcat 中运⾏:${Tomcat_home}/bin/⽬录下执⾏ startup.bat(windows)或者 startup.sh(linux),然后通过浏览器访问应⽤,测试效果。

需要注意的是

  • 在 boot-launch.war 在 tomcat webapps ⽬录⾥⾯解压到 boot-launch ⽂件夹。所以访问应⽤的时 候,必须使⽤http://localhost:8888/boot-launch/template/thymeleaf 不能是:http://localhost:8888/template/thymeleaf,会报 404 错误。
  • 静态资源引⽤也必须是:/boot-launch/image/xxxx.png,不能是/image/xxxx.png
  • JSP 的 war 包中,webjars 的资源使⽤⽅式不再被⽀持

文章作者: Syhan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Syhan !
评论
  目录