Appearance
优雅关闭
概述
优雅关闭是现代 Web 应用程序中一个至关重要的特性,它确保应用程序在关闭时能够妥善处理正在进行的请求。在 Spring Boot 中,优雅关闭功能默认启用,支持所有四种嵌入式 Web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow),同时兼容响应式和 Servlet 架构的 Web 应用程序。
为什么需要优雅关闭?
在生产环境中,如果直接强制关闭应用程序,可能会导致以下问题:
- 正在处理的请求被突然中断,造成数据不一致
- 用户收到错误响应,影响用户体验
- 数据库事务被意外中断,可能造成数据损坏
- 资源(如文件句柄、网络连接)无法正确释放
优雅关闭的工作原理
优雅关闭作为应用程序上下文关闭过程的一部分执行,在停止 SmartLifecycle
Bean 的最早阶段进行。这个停止过程使用一个超时时间,提供一个宽限期,在此期间:
- 现有请求:允许继续完成处理
- 新请求:不再接受新的请求
- 超时控制:提供合理的等待时间避免无限等待
配置超时时间
可以通过配置 spring.lifecycle.timeout-per-shutdown-phase
属性来设置超时时间:
properties
# 设置优雅关闭超时时间为 20 秒
spring.lifecycle.timeout-per-shutdown-phase=20s
yaml
spring:
lifecycle:
# 设置优雅关闭超时时间为 20 秒
timeout-per-shutdown-phase: "20s"
IMPORTANT
在 IDE 中直接停止应用程序可能会立即关闭而不是优雅关闭,因为 IDE 可能不会发送正确的 SIGTERM
信号。请查看您的 IDE 文档获取更多详细信息。
业务场景示例
电商订单处理场景
假设我们有一个电商系统,正在处理用户的订单支付:
kotlin
@RestController
@RequestMapping("/api/orders")
class OrderController(
private val orderService: OrderService,
private val paymentService: PaymentService
) {
@PostMapping("/{orderId}/pay")
fun processPayment(@PathVariable orderId: Long, @RequestBody paymentRequest: PaymentRequest): ResponseEntity<PaymentResponse> {
return try {
// 这个过程可能需要 5-10 秒时间
val order = orderService.findById(orderId) // 查询订单 - 1秒
val paymentResult = paymentService.processPayment(paymentRequest) // 调用第三方支付 - 3-8秒
orderService.updateOrderStatus(orderId, OrderStatus.PAID) // 更新订单状态 - 1秒
ResponseEntity.ok(PaymentResponse(paymentResult.transactionId, "支付成功"))
} catch (ex: Exception) {
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(PaymentResponse(null, "支付失败: ${ex.message}"))
}
}
}
kotlin
@Service
@Transactional
class PaymentService {
fun processPayment(paymentRequest: PaymentRequest): PaymentResult {
// 模拟调用第三方支付接口,可能需要较长时间
return try {
// 1. 验证支付信息
validatePaymentInfo(paymentRequest)
// 2. 调用第三方支付网关(可能需要 3-8 秒)
val thirdPartyResponse = callThirdPartyPaymentGateway(paymentRequest)
// 3. 保存支付记录
savePaymentRecord(paymentRequest, thirdPartyResponse)
PaymentResult(thirdPartyResponse.transactionId, PaymentStatus.SUCCESS)
} catch (ex: PaymentException) {
PaymentResult(null, PaymentStatus.FAILED)
}
}
private fun callThirdPartyPaymentGateway(request: PaymentRequest): ThirdPartyPaymentResponse {
// 模拟网络延迟和第三方处理时间
Thread.sleep(5000) // 实际场景中这是网络请求
return ThirdPartyPaymentResponse("TXN_${System.currentTimeMillis()}")
}
}
在这种场景下,如果应用程序需要关闭:
- 没有优雅关闭:正在处理的支付请求被强制中断,用户支付状态不明确
- 有优雅关闭:系统等待支付处理完成后再关闭,确保数据一致性
在宽限期内拒绝新请求的处理方式
不同的 Web 服务器在宽限期内拒绝新请求的具体方式有所不同:
Web 服务器 | 处理方式 |
---|---|
Jetty | 在网络层停止接受新请求 |
Reactor Netty | 在网络层停止接受新请求 |
Tomcat | 在网络层停止接受新请求 |
Undertow | 接受新连接但立即返回 503 (Service Unavailable) 响应 |
TIP
要了解您的 Web 服务器使用的具体方法,请查看相应的 shutDownGracefully
API 文档:
TomcatWebServer.shutDownGracefully(GracefulShutdownCallback)
NettyWebServer.shutDownGracefully(GracefulShutdownCallback)
JettyWebServer.shutDownGracefully(GracefulShutdownCallback)
UndertowWebServer.shutDownGracefully(GracefulShutdownCallback)
监控优雅关闭过程
可以通过实现自定义的监听器来监控优雅关闭过程:
kotlin
@Component
class GracefulShutdownListener : ApplicationListener<ContextClosedEvent> {
private val logger = LoggerFactory.getLogger(GracefulShutdownListener::class.java)
override fun onApplicationEvent(event: ContextClosedEvent) {
logger.info("应用程序开始优雅关闭...")
// 可以在这里添加额外的清理逻辑
performCustomCleanup()
}
private fun performCustomCleanup() {
// 清理缓存
logger.info("清理应用程序缓存...")
// 关闭自定义线程池
logger.info("关闭自定义线程池...")
// 发送关闭通知
logger.info("发送应用关闭通知...")
}
}
自定义优雅关闭处理器
对于需要特定关闭逻辑的组件,可以实现 SmartLifecycle
接口:
kotlin
@Component
class CustomGracefulShutdownHandler : SmartLifecycle {
private val logger = LoggerFactory.getLogger(CustomGracefulShutdownHandler::class.java)
private var running = false
override fun start() {
logger.info("启动自定义优雅关闭处理器")
running = true
}
override fun stop() {
logger.info("开始执行自定义关闭逻辑...")
// 执行业务相关的清理工作
cleanupBusinessResources()
running = false
logger.info("自定义关闭逻辑执行完成")
}
override fun isRunning(): Boolean = running
override fun getPhase(): Int {
// 返回较小的值以确保在其他组件之前关闭
return SmartLifecycle.DEFAULT_PHASE - 1000
}
private fun cleanupBusinessResources() {
try {
// 示例:关闭消息队列连接
logger.info("关闭消息队列连接...")
Thread.sleep(2000) // 模拟清理时间
// 示例:保存临时数据
logger.info("保存临时数据...")
Thread.sleep(1000)
logger.info("业务资源清理完成")
} catch (ex: Exception) {
logger.error("业务资源清理失败", ex)
}
}
}
禁用优雅关闭
在某些场景下(如开发环境或测试环境),您可能希望禁用优雅关闭以加快应用程序关闭速度:
properties
# 禁用优雅关闭,立即关闭应用程序
server.shutdown=immediate
yaml
server:
shutdown: "immediate"
WARNING
在生产环境中不建议禁用优雅关闭,因为这可能导致数据不一致和用户体验问题。
最佳实践
1. 合理设置超时时间
kotlin
// 根据业务需求设置合适的超时时间
// 对于简单的 CRUD 操作:5-10 秒
// 对于复杂的业务处理:20-30 秒
// 对于批处理操作:60-120 秒
2. 实现健康检查端点
kotlin
@RestController
class HealthController {
@Autowired
private lateinit var applicationContext: ConfigurableApplicationContext
@GetMapping("/health/shutdown")
fun getShutdownStatus(): ResponseEntity<Map<String, Any>> {
val status = mapOf(
"shutdownInitiated" to !applicationContext.isActive,
"timestamp" to Instant.now().toString()
)
return ResponseEntity.ok(status)
}
}
3. 配置负载均衡器
NOTE
优雅关闭是构建可靠 Web 应用程序的重要特性。通过合理配置和监控,可以确保应用程序在维护或升级时不会影响用户体验和数据完整性。