zl程序教程

您现在的位置是:首页 >  Java

当前栏目

重学SpringBoot系列之嵌入式容器的配置与应用

2023-04-18 14:29:34 时间

重学SpringBoot系列之嵌入式容器的配置与应用

嵌入式容器的运行参数配置

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

调整SpringBoot应用容器的参数两种配置方法

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

配置文件方式

在application.properties / application.yml可以配置Web 容器运行所需要的属性,可以通过该链接在官方网站查看关于server的所有配置项:server-properties。

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

常用配置参数


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在应用空闲时,保留一定的线程数在线程池内。避免请求到来后,临时创建线程浪费时间。

自定义配置类方式

步骤:

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熟练度。


为Web容器配置HTTPS

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

可以通过在Web应用程序上安装SSL证书来实现HTTPS,互联网上受信任的证书通常是需要(CA)认证机构颁发的证书(通常是收费的)。一个标准的SSL证书,还是有点小贵的。国内的一些厂商虽然可以提供免费的证书,但是都有一定的免费时效性限制。

如果是以学习为目的,我们也可以使用自签名证书,即:使用Java Keytool生成自签名证书。完全不需要购买CA机构认证的SSL证书。


如何生成自签名证书

在Windows的搜索字段中键入cmd以找到命令提示符,然后以“以管理员身份运行”右键单击。使用如下的keytool命令。您可以提及所需的证书名称,如下所示。

keytool工具是jdk里面自带的,如果做了环境变量的配置,那么可以不用在jdk bin目录下执行keytool命令

keytool -genkeypair 
-alias selfsigned_localhost_sslserver 
-keyalg RSA 
-keysize 2048 
-storetype PKCS12 
-keystore dhy-ssl-key.p12 
-validity 3650
命令参数说明:

-genkey:表示要创建一个新的密钥
-alias:表示keystore的别名
-keyalg:表示使用的加密算法是RSA(一种非对称加密算法)
-keysize:表示密钥的长度
-storetype: 证书的类型
-keystore:表示生成的密钥存放位置
-validity:表示密钥的有效时间(单位为天)

自签名证书受密码保护。命令回车之后,会提示输入密码(这个密码要记住,后面会用到)和其他详细信息,如以下屏幕截图所示。

完成上述步骤后,便会创建PKS密钥并将其存储在当前命令行所在的目录下。


将SSL应用于Spring Boot应用程序

从JDK bin文件夹复制dhy-ssl-key并将其放在Spring Boot Application的src/main/resources下。 如下所示,将SSL密钥信息添加到application.yml中。

server:
  ssl:
    key-store: src/main/resources/dhy-ssl-key.p12 或者 classpath: dhy-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

注意https的默认端口是443

http默认端口是80


ssl证书配置可参考文章

使用JDK自带工具keytool生成ssl证书

Springboot配置ssl证书踩坑记

使用JDK中的 keytool【创建证书】・【查看】・【使用】


切换到jetty&undertow容器

虽然可以使用jetty或者undertow替换掉tomcat,但是笔者不建议这么做,也从来没这么做过。可能在某些场景下,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多线程模型

  • mainReactor负责监听server socket,用来处理新连接的建立,将建立的socketChannel指定注册给subReactor。
  • subReactor维护自己的selector, 基于mainReactor注册的socketChannel多路分离IO读写事件,读写网络数据,对业务处理的功能,另其扔给worker线程池来完成。

切换为 Jetty Server

常用jetty调优配置参数

acceptors可以理解为产品经理,负责处理接收请求的人

selectors可以理解为项目经理,负责把产品经理接收到的请求分发给下面的程序员完成

min<程序员的数量<max :真正干活的线程


切换到undertow

下文配置中的io-threads可以认为是acceptor线程数,用来出来连接的建立。worker-threads就是工作线程池的线程数量。

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

嵌入式容器详细参考文章

SpringBoot----嵌入式Servelt容器


打war包部署到外置tomcat容器

修改打包方式

<packaging>war</packaging>

将上面的代码加入到pom.xml文件刚开始的位置,如下:


排除内置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>

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <!--打包的时候可以不用包进去,别的设施会提供。事实上该依赖理论上可以参与编译,测试,运行等周期。
        相当于compile,但是打包阶段做了exclude操作-->
    <scope>provided</scope>
</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服务器,那么你根本不需要这个。

方式一:新增加一个类继承SpringBootServletInitializer实现configure:

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

方式二:启动类继承SpringBootServletInitializer实现configure:

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

注意事项: 使用外部Tomcat部署访问的时候,application.properties(或者application.yml)中的如下配置将失效,请使用外置的tomcat的端口,tomcat的webapps下项目名进行访问。

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

配置文件里面对tomcat的设置,只在使用内置的tomcat容器时候生效,当使用外置tomcat的时候,会失效


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/jsp,不能是:http://localhost:8888/template/jsp。会报404错误。
  • jsp静态资源引用也必须是:/boot-launch/image/xxxx.png,不能是/image/xxxx.png
  • JSP的war包中,webjars的资源使用方式不再被支持