Java層如何配合K8s實現(xiàn)優(yōu)雅下線
在Kubernetes(K8s)中部署Java應用(如Spring Boot)時,實現(xiàn)優(yōu)雅下線(Graceful Shutdown)是確保服務平滑停止的關鍵,避免正在處理的請求中斷或數(shù)據(jù)不一致,尤其在金融、支付等高可靠性場景中至關重要。優(yōu)雅下線需要Java應用層與K8s的生命周期管理機制協(xié)同工作。以下是詳細實現(xiàn)方案,涵蓋原理、代碼實現(xiàn)和配置步驟。
一、優(yōu)雅下線的原理
1. K8s下線流程
- 當K8s執(zhí)行kubectl delete或滾動更新(Rolling Update)時:
- Pod標記為Terminating:K8s向Pod發(fā)送SIGTERM信號。
- 負載均衡移除:Service從Endpoint中移除該Pod,停止新流量。
- 寬限期等待:K8s等待terminationGracePeriodSeconds(默認30秒),然后發(fā)送SIGKILL強制終止。
- 目標:
在寬限期內(nèi)完成現(xiàn)有請求處理,拒絕新請求,釋放資源。
2. Java層需求
- 捕獲SIGTERM:監(jiān)聽操作系統(tǒng)信號,觸發(fā)關閉邏輯。
- 停止新請求:關閉Web服務器(如Tomcat),但保留現(xiàn)有連接。
- 完成任務:等待異步任務(如數(shù)據(jù)庫寫入)結束。
- 通知K8s:通過健康檢查(Readiness Probe)告知已下線。
二、Java層實現(xiàn)優(yōu)雅下線
以Spring Boot為例,結合K8s的preStop鉤子和Spring的關閉機制實現(xiàn)。
1. 捕獲SIGTERM信號
- Spring Boot通過ApplicationListener監(jiān)聽上下文關閉事件:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationContextInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("Received SIGTERM, starting graceful shutdown...");
// 自定義關閉邏輯
try {
Thread.sleep(5000); // 模擬等待現(xiàn)有請求完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Shutdown complete.");
}
}
2. 關閉Web服務器
- Spring Boot嵌入Tomcat時,需優(yōu)雅停止連接:
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
@Component
public class TomcatGracefulShutdown implements ApplicationListener<ContextClosedEvent>, TomcatConnectorCustomizer {
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (connector != null) {
System.out.println("Shutting down Tomcat gracefully...");
connector.pause(); // 暫停新請求
try {
Thread.sleep(5000); // 等待現(xiàn)有請求完成,實際應動態(tài)判斷
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
connector.getService().stop(); // 停止服務
}
}
}
- 配置Spring Boot:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addListeners(new TomcatGracefulShutdown());
app.run(args);
}
}
3. 處理異步任務
- 若有線程池或消息隊列(如Kafka消費者),需等待任務完成:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
@Component
public class AsyncTaskShutdown implements ApplicationListener<ContextClosedEvent> {
@Autowired
private ThreadPoolTaskExecutor executor;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("Shutting down async tasks...");
executor.setWaitForTasksToCompleteOnShutdown(true); // 等待任務完成
executor.setAwaitTerminationSeconds(10); // 最多等10秒
executor.shutdown();
}
}
三、K8s配置配合
1. 設置寬限期
- 在Pod配置中延長terminationGracePeriodSeconds,給Java足夠關閉時間:
apiVersion: v1
kind: Pod
metadata:
name: spring-boot-app
spec:
containers:
- name: app
image: spring-boot-app:latest
terminationGracePeriodSeconds: 60 # 寬限期60秒
2. 添加preStop鉤子
- 在容器停止前執(zhí)行腳本,通知應用準備下線:
spec:
containers:
- name: app
image: spring-boot-app:latest
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "curl -X POST http://localhost:8080/actuator/shutdown"]
ports:
- containerPort: 8080
- Spring Boot啟用Actuator:
management.endpoint.shutdown.enabled=true
management.endpoints.web.exposure.include=shutdown
- 說明:preStop調(diào)用/actuator/shutdown,觸發(fā)Spring上下文關閉。
3. 配置Readiness Probe
- 讓K8s感知應用不再就緒,移除流量:
spec:
containers:
- name: app
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
- Java代碼:關閉時更新健康狀態(tài):
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class ShutdownHealthIndicator implements HealthIndicator {
private volatile boolean isShuttingDown = false;
public void setShuttingDown(boolean shuttingDown) {
this.isShuttingDown = shuttingDown;
}
@Override
public Health health() {
return isShuttingDown ? Health.down().build() : Health.up().build();
}
}
@Component
public class ShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Autowired
private ShutdownHealthIndicator healthIndicator;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
healthIndicator.setShuttingDown(true); // 標記下線
}
}
四、完整流程
- K8s發(fā)起停止:
- 發(fā)送SIGTERM,觸發(fā)preStop。
- Java響應:
- /actuator/shutdown關閉Spring上下文。
- Readiness Probe返回DOWN,K8s移除流量。
- Tomcat暫停新請求,等待現(xiàn)有請求完成。
- 異步任務執(zhí)行完畢。
- Pod終止:
- 寬限期(60秒)內(nèi)完成,K8s發(fā)送SIGKILL。
五、驗證與優(yōu)化
- 測試:
kubectl delete pod spring-boot-app
# 檢查日志,確保"Shutdown complete"打印,且無請求中斷
- 優(yōu)化:
動態(tài)等待:根據(jù)活躍連接數(shù)(Tomcat getActiveCount)調(diào)整睡眠時間。
超時控制:若任務未完成,記錄日志并強制退出。
六、總結
- Java層:通過ContextClosedEvent捕獲信號,優(yōu)雅關閉Tomcat和異步任務。
- K8s配合:設置terminationGracePeriodSeconds、preStop和Readiness Probe,確保流量移除和資源釋放。
- 效果:請求零中斷,數(shù)據(jù)一致性保障。