線上服務(wù)如何優(yōu)雅停機(jī)?
前言
最近星球中有位小伙伴問(wèn)了我一個(gè)問(wèn)題:如何優(yōu)雅的停機(jī)?
我覺(jué)得這個(gè)問(wèn)題挺有代表性的。
今天這篇文章跟大家一下優(yōu)雅停機(jī)的一些常見(jiàn)方案,希望對(duì)你會(huì)有所幫助。
1.什么是優(yōu)雅停機(jī)?
優(yōu)雅停機(jī)(Graceful Shutdown) 指在服務(wù)終止前,系統(tǒng)能:
- 拒絕新請(qǐng)求進(jìn)入
- 完成存量請(qǐng)求處理
- 釋放所有資源
- 通知上下游服務(wù)
非優(yōu)雅停機(jī)的慘痛代價(jià):
圖片
真實(shí)案例:支付回調(diào)丟失。
// 支付回調(diào)處理
@PostMapping("/callback")
public void handleCallback(Payment payment) {
// 1. 更新訂單狀態(tài)
orderService.updateStatus(payment.getOrderId(), PAID);
// 2. 發(fā)放權(quán)益(kill發(fā)生時(shí)此處未執(zhí)行)
benefitService.grantVip(payment.getUserId());
}當(dāng)kill發(fā)生在步驟1和2之間時(shí),導(dǎo)致訂單狀態(tài)已更新但權(quán)益未發(fā)放,引發(fā)用戶投訴。
2.優(yōu)雅停機(jī)三大核心流程
2.1 信號(hào)捕獲層
圖片
2.2 流量控制層
圖片
2.3 資源釋放層
圖片
3.Spring Boot優(yōu)雅停機(jī)的實(shí)現(xiàn)
3.1 基礎(chǔ)配置
在SpringBoot項(xiàng)目的application.yml文件中增加如下配置:
server:
shutdown: graceful # 開啟優(yōu)雅停機(jī)
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 最長(zhǎng)等待時(shí)間3.2 線程池優(yōu)雅關(guān)閉
在線程池中實(shí)現(xiàn)優(yōu)雅關(guān)閉功能:
@Bean
public ExecutorService threadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setWaitForTasksToCompleteOnShutdown(true); // 等待任務(wù)完成
executor.setAwaitTerminationSeconds(60); // 最大等待時(shí)間
return executor.getThreadPoolExecutor();
}在shutdown之前,先等待任務(wù)完成。
3.3 分布式鎖釋放攔截器
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object handleRequest(ProceedingJoinPoint pjp) {
Lock lock = redisson.getLock("order_lock");
try {
lock.lock();
return pjp.proceed();
} finally {
if (!isShuttingDown()) {
lock.unlock(); // 非停機(jī)時(shí)正常釋放
}
// 停機(jī)時(shí)由鎖管理器統(tǒng)一釋放
}
}使用統(tǒng)一的攔截器釋放分布式鎖,防止出現(xiàn)異常有釋放遺漏的地方。
4.Kubernetes環(huán)境下的優(yōu)雅停機(jī)
4.1 關(guān)鍵配置
STOPSIGNAL SIGTERM # 使用SIGTERM替代SIGKILL# Deployment配置
spec:
terminationGracePeriodSeconds:60# 寬限期
containers:
-lifecycle:
preStop:
exec:
command:["/bin/sh","-c","sleep 20;"]# 預(yù)留緩沖時(shí)間在部署配置中增加預(yù)留緩沖時(shí)間。
4.2 就緒探針自動(dòng)摘流
圖片
5.中間件連接優(yōu)雅關(guān)閉
5.1 數(shù)據(jù)庫(kù)連接池
@PreDestroy
public void close() {
HikariPool pool = dataSource.getHikariPoolMXBean();
pool.suspendPool(); // 停止借出連接
pool.softEvictConnections(); // 驅(qū)逐空閑連接
while (pool.getActiveConnections() > 0) {
Thread.sleep(500); // 等待活動(dòng)連接完成
}
pool.shutdown(); // 徹底關(guān)閉
}使用@PreDestroy在服務(wù)銷毀之前關(guān)閉數(shù)據(jù)庫(kù)連接池。
5.2 RabbitMQ消費(fèi)者
@PreDestroy
public void stop() {
channel.basicCancel(consumerTag); // 取消訂閱
while (unackedMessages.get() > 0) {
Thread.sleep(100); // 等待ACK完成
}
connection.close();
}@PreDestroy在服務(wù)銷毀之前取消訂閱,需要先等待ACK完成。
3. Redis分布式鎖
public class LockManager implements DisposableBean {
@Override
public void destroy() {
lockMap.forEach((key, lock) -> {
if (lock.isHeldByCurrentThread()) {
lock.unlock(); // 強(qiáng)制釋放未解鎖的鎖
}
});
}
}實(shí)現(xiàn)DisposableBean接口,在服務(wù)銷毀之前強(qiáng)制釋放未解鎖的鎖。
6.全鏈路優(yōu)雅停機(jī)
6.1 停機(jī)事件傳播機(jī)制
圖片
6.2 狀態(tài)機(jī)管理
public enum ShutdownState {
RUNNING, // 正常運(yùn)行
PRE_SHUTDOWN, // 拒絕新請(qǐng)求
DRAINING, // 排空存量請(qǐng)求
TERMINATED // 完全終止
}6.3 停機(jī)監(jiān)控面板
圖片
7.生產(chǎn)環(huán)境避坑指南
7.1 必須避免的四大陷阱
陷阱 | 后果 | 解決方案 |
死鎖等待 | 無(wú)法完成停機(jī) | 設(shè)置鎖超時(shí)時(shí)間 |
第三方服務(wù)不可用 | 資源無(wú)法釋放 | 添加熔斷機(jī)制 |
長(zhǎng)周期任務(wù) | 超過(guò)寬限期被強(qiáng)殺 | 拆分任務(wù)+保存中間狀態(tài) |
文件寫入未完成 | 數(shù)據(jù)損壞 | 使用原子文件替換 |
7.2 停機(jī)檢查清單
# 停機(jī)前執(zhí)行
curl -X POST http://localhost:8080/actuator/shutdown-prepare
# 驗(yàn)證項(xiàng):
1. 新請(qǐng)求返回503
2. 活動(dòng)線程數(shù)持續(xù)下降
3. 數(shù)據(jù)庫(kù)連接數(shù)歸零
4. MQ無(wú)未ACK消息7.3 黃金法則:二段式停機(jī)
圖片
總結(jié)
- 基礎(chǔ)層:處理HTTP請(qǐng)求Spring Boot Graceful Shutdown + 線程池等待
- 進(jìn)階層:管理中間件連接數(shù)據(jù)庫(kù)連接池排空 + MQ消費(fèi)者取消訂閱
- 高級(jí)層:分布式協(xié)同停機(jī)事件廣播 + 分布式鎖釋放
- 終極層:全鏈路狀態(tài)管理停機(jī)狀態(tài)機(jī) + 智能超時(shí)控制
停機(jī)策略對(duì)比表
策略 | 實(shí)現(xiàn)難度 | 停機(jī)時(shí)間 | 數(shù)據(jù)安全 | 適用場(chǎng)景 |
直接kill -9 | ☆ | 秒級(jí) | 極低 | 開發(fā)環(huán)境 |
Spring Boot | ☆☆ | 10-30s | 中 | 常規(guī)Web應(yīng)用 |
容器化方案 | ☆☆☆ | 可配置 | 高 | K8S環(huán)境 |
全鏈路管理 | ☆☆☆☆ | 分鐘級(jí) | 極高 | 金融核心系統(tǒng) |






























