嵌⼊式容器的配置与应⽤
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 | 最⼩的⼯作线程池数量 |
⼆、⾃定义配置类⽅式
步骤:
建⽴⼀个配置类,加上@Configuration 注解
添加定制器 ConfigurableServletWebServerFactory
将定制器返回
@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 应⽤程序
- 复制 syhan-ssl-key,将其放在应⽤根⽬录下。
- 将 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 多线程模型
- mainReactor 负责监听 server socket,⽤来处理新连接的建⽴,将建⽴的 socketChannel 指定注册给 subReactor。
- 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 的资源使⽤⽅式不再被⽀持