SpringBoot:嵌入式Web Server配置与优雅停机
WebServer简介
嵌入式Web Server
Spring Boot 支持以下几种嵌入式 web 服务器:
- Tomcat - 默认使用的服务器,轻量级,简单易用。
- Jetty - 高性能、资源消耗少的服务器,常用于生产环境。
- Undertow - 基于 NIO 的服务器,性能很高,启动很快。
- Netty - 不是严格意义上的 web 服务器,而是基于 NIO 的网络应用框架,性能很高,可以用来开发 web 服务器。
默认Web Server加载行为
Spring Boot 决定使用哪个 Web 服务器,是根据所添加的依赖项来进行决定的。
Spring Boot 默认使用 Tomcat 作为 Web 服务器,如果添加了 spring-boot-starter-web
依赖,则会自动引入 Tomcat 依赖,并使用 Tomcat 作为 Web 服务器。
如果添加了其他 Web 服务器的依赖项,例如 Jetty 或 Undertow,Spring Boot 将自动配置相应的 EmbeddedServletContainerFactory,使用相应的 Web 服务器。例如,如果添加了 spring-boot-starter-jetty
依赖,则会自动引入 Jetty 依赖,并使用 Jetty 作为 Web 服务器。
如果需要手动更换默认的 Web 服务器,可以通过编写自定义的 EmbeddedServletContainerFactory 实现。(从 Spring Boot 2.0 开始,EmbeddedServletContainerFactory
已被弃用,取而代之的是 ConfigurableReactiveWebServerFactory
)
加载并配置Web Server
Tomcat
Spring Boot 默认使用 Tomcat 作为 Web 服务器
添加依赖
dependencies {
// ...
implementation 'org.springframework.boot:spring-boot-starter-web'
}
配置Tomcat属性
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(ThreadPoolExecutor.class)
public class TomcatConfiguration {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(connector -> {
// 获取 Tomcat 的 ProtocolHandler
org.apache.coyote.ProtocolHandler protocolHandler = connector.getProtocolHandler();
// 设置线程池属性
if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
org.apache.coyote.http11.AbstractHttp11Protocol<?> httpProtocol = (org.apache.coyote.http11.AbstractHttp11Protocol<?>) protocolHandler;
// 设置最大线程数
httpProtocol.setMaxThreads(200);
// 设置最小空闲线程数
httpProtocol.setMinSpareThreads(10);
// 设置连接超时
httpProtocol.setConnectionTimeout(30000);
// 设置其他线程池属性...
}
});
return factory;
}
}
Jetty
添加依赖
修改 build.gradle
文件,移除 Tomcat 依赖,并添加 Jetty 依赖:
dependencies {
// ...
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jetty'
// 移除 Tomcat 依赖
configurations {
compile.exclude module: 'spring-boot-starter-tomcat'
}
}
我们首先添加了 spring-boot-starter-jetty
依赖,并使用 configurations
部分从编译依赖中排除了 spring-boot-starter-tomcat
。
现在,当你运行应用程序时,应该会使用 Jetty 作为嵌入式服务器,而不是默认的 Tomcat。
配置Jetty属性
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JettyConfiguration {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
// 在这里对 Jetty 服务器进行自定义设置,例如:
// factory.setPort(8080);
return factory;
}
}
在这个配置类中,我们创建了一个 JettyServletWebServerFactory
的实例,并将其注册为 Spring Bean。你可以在这里对 Jetty 服务器进行一些自定义配置,比如设置端口号等。
Spring WebFlux(Reactive) 和 Spring Web MVC(Servlet)
Spring WebFlux 和 Spring Web MVC 是 Spring 框架中用于构建 Web 应用程序的两种不同方法。它们分别基于 Reactive 和 Servlet 技术栈。
Spring WebFlux 是 Spring 5.0 引入的一个新的响应式 Web 框架。它基于 Reactive Streams 规范,提供了非阻塞的、事件驱动的、可组合的 API,旨在支持异步和高效的数据处理。WebFlux 使用 Reactor 库作为底层响应式库。它适用于高并发、低延迟的场景,如实时数据处理、聊天应用、物联网应用等。
WebFlux 支持两种编程模型:
- 注解式编程模型:与 Spring Web MVC 类似,使用
@Controller
和@RequestMapping
等注解。 - 函数式编程模型:使用函数式风格的路由和处理器定义。
Spring Web MVC 是一个基于 Servlet API 的 Web 框架,它使用阻塞 I/O,通常与线程池一起使用以支持并发请求。Spring Web MVC 在每个请求处理过程中为每个线程分配一个线程。这种方法在一定程度上限制了可伸缩性,尤其是在高并发场景下。
Reactive 和 Servlet 的区别:
- 编程模型:Reactive 使用响应式编程模型,提供非阻塞、事件驱动的 API,支持异步数据处理。Servlet 则使用阻塞式编程模型,每个请求都由一个线程处理。
- 性能和可伸缩性:由于 WebFlux 的非阻塞特性,它能更好地应对高并发、低延迟的场景。在高负载下,WebFlux 可以更有效地利用系统资源,提供更高的吞吐量。而 Servlet 在高并发场景下可能受到线程池大小的限制。
- API 风格:WebFlux 提供了基于响应式类型(如
Flux
和Mono
)的 API,这些类型使得数据流的处理变得更加灵活和可组合。Servlet API 主要基于阻塞式 I/O。 - 数据处理:Reactive 支持处理无界数据流和背压(backpressure)机制,允许消费者控制生产者的数据生成速度。Servlet 没有这些特性。
优雅停机
优雅停机(Graceful Shutdown)是指在服务停止过程中,确保已接收的请求得到完整处理,同时拒绝新的请求。这样可以在服务关闭或更新时,减少对用户和客户端的影响,避免数据丢失或出现不一致的状态。
优雅停机的主要步骤如下:
- 停止接收新的请求:当服务开始关闭时,它会停止接收新的请求。这可以通过关闭监听端口、拒绝新的连接或关闭负载均衡器等方式实现。
- 等待现有请求完成:服务关闭前,需要等待正在处理的请求完成。这包括等待数据库操作、文件 I/O 或其他异步任务完成。
- 设置超时:为了避免长时间等待,可以设置一个超时时间。如果在超时时间内未处理完所有请求,服务将强制关闭。这个时间应该根据应用程序的特点和需求来设置。
- 释放资源:在所有请求处理完成后,服务应释放所有占用的资源,如数据库连接、缓存等。这有助于确保应用程序在关闭时不会导致资源泄露。
优雅停机原理
讨论 JVM 优雅停机原理之前,先了解一下 JVM 的停止方式。JVM 停止主要有以下几种情况:
- 执行完所有非守护线程(non-daemon threads)后自然退出。
- 调用
System.exit()
方法。 - 接收到操作系统发送的中断信号(如 SIGTERM)。
JVM 优雅停机的目标是确保在 JVM 停止过程中,程序能够完整地处理已接收的请求,释放资源,关闭连接等,避免数据丢失或产生不一致的状态。
JVM 实现优雅停机的主要机制是通过关闭钩子(Shutdown Hook)。关闭钩子是一个线程,它在JVM 收到关闭信号时执行。你可以通过 Runtime.getRuntime().addShutdownHook(Thread hook)
方法向 JVM 注册一个或多个关闭钩子。当 JVM 收到关闭信号(如 SIGTERM)时,它会启动所有已注册的关闭钩子线程,并等待它们执行完成。在关闭钩子线程中,可以实现以下任务:
- 处理正在进行的请求,等待它们完成。
- 停止接收新的请求。
- 释放资源,如数据库连接、线程池、缓存等。
- 设置超时,以避免无限期等待。
SpringBoot实现优雅停机
application.yml
在 Java 和 Spring Boot 应用程序中,可以借助以下配置实现优雅停机:
# application.yml
server:
shutdown: graceful
graceful-shutdown-timeout: 30s
这里,我们通过 server.shutdown
属性设置为 graceful
开启优雅停机,通过 server.graceful-shutdown-timeout
属性设置超时时间为 30 秒。当收到关闭信号时,Spring Boot 会按照上述步骤优雅地关闭服务。
注册shutdown hook(简单版本)
SpringApplication springApplication = new SpringApplication(XXApplication.class);
ConfigurableApplicationContext ctx = springApplication.run(args);
Runtime.getRuntime().addShutdownHook(
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10000L);
} catch (InterruptedException e) {
log.error("关机发生异常", e);
Thread.currentThread().interrupt();
}
ctx.close();
}));
注册shutdown hook(复杂版本)
创建一个 TomcatShutdown
类
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Component
public class TomcatShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final int TIMEOUT = 30; // 超时时间,单位:秒
@Autowired
private Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
shutdownGracefully();
}
public void shutdownGracefully() {
if (connector == null) {
return;
}
connector.pause();
Executor executor = connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) {
System.err.println("Tomcat 进程在 " + TIMEOUT + " 秒内无法停止,执行强制关闭。");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
添加关闭钩子
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatShutdown;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class GracefulShutdownDemoApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(GracefulShutdownDemoApplication.class);
ConfigurableApplicationContext context = application.run(args);
// 添加优雅停机关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
context.getBean(TomcatShutdown.class).shutdownGracefully();
context.close();
}));
}
}
转载自:https://juejin.cn/post/7243715129120079930