前幾天我們完成了微服務容器化(Docker)、Prometheus/Grafana 監控服務建立、破除長 Timeout 迷思、並透過壓力測試驗證了listObject和PutObject的防禦效能。非同步 S3 微服務不但在 1.5 核 CPU 限制下展現了驚人的吞吐量,更能彈性擋下流量海嘯與網路波動。
下一步應該要準備把 Docker 容器交付給 DevOps 團隊,準備部署到雲端進行正式上線與滾動更新(Rolling Update),但新的問題又出現了:
如果在 CI/CD 發佈新版本或是系統進行滾動更新重啟容器時,如果背景剛好有 10 個使用者正在上傳大檔案,容器突然被停掉,這些正在傳輸到一半的 S3 非同步封包會怎樣?
如果沒有經過特殊處理,這些請求會被當場掐斷!客戶端會收到沒有狀態的A Non HTTP reponse code 報錯,S3 上還可能會留下傳到一半的孤兒碎片,使用者體驗極差無比!
所以今天我們要來實作優雅停機(Graceful Shutdown)機制,確保在系統收到關機訊號時,能夠拒絕新客、清空舊客,達成 0 封包遺失的平滑滾動更新!
在傳統單體架構時代,每次要更換新版本程式碼,常常需要在半夜兩點公告「系統維護中,停機 2 小時」。但在現代 24/7 不間斷運作的雲端微服務中,零停機部署(Zero-Downtime Deployment)已成為企業 SLA (99.99% 高可用) 的基本門檻。
零停機部署的核心目標是:在不中斷對外服務、使用者完全無感的情況下,將線上運行中的舊版本 (v1) 平滑替換為新版本 (v2)。常見方法有三 :
我們常以為在設定了 Rolling Update,或是使用了藍綠部署,系統就自動零停機了!事實卻不然,思考一下當負載均衡器 (Load Balancer) 切離流量或遇到問題中斷時:
如果舊容器在收到關機通知時,沒有具備優雅停機 (Graceful Shutdown) 能力,作業系統會直接下達 kill -9 (SIGKILL) 強制拔插頭。結果就是這 10 個正在處理中的使用者立刻收到 5xx 警報!
💡 零停機部署黃金公式:零停機部署 = 上游流量切換 (藍綠 / 滾動更新) + 下游應用優雅停機 (Graceful Shutdown)兩者缺一不可,才能達成真正 0 封包遺失的平滑發佈!
要理解優雅停機,我們必須先搞懂 Linux 作業系統是如何向進程(Process)發送關機訊號的。
左側 SIGKILL (Signal 9 / kill -9) 暴力中斷:
右側 SIGTERM (Signal 15 / kill -15) 禮貌通知:
503 Service Unavailable 或秒退,不再接納任何新乘客。
我們用一張時序圖來呈現上述當kill -15禮貌通知,系統內部如何精準執行兩階段退場:

Spring Boot 中要開啟這個兩階段優雅停機非常簡單,我們只需要在 application.yml 裡進行宣告:
server:
port: 8080
# 1. 啟用優雅停機機制 (預設為 immediate 立即暴力關閉)
shutdown: graceful
spring:
lifecycle:
# 2. 設定優雅停機各階段的最大等待緩衝期 (Grace Period)
# 给予背景 S3 Async Client 與 Netty EventLoop 最多 30 秒完成手頭上的傳輸
timeout-per-shutdown-phase: 30s
現在,我們打開 Terminal,親眼見證有沒有優雅停機在重啟容器時的差異
docker ps 查看my-app微服務的conatiner iddocker kill --signal=SIGKILL <container_id>
s3-app exited with code 137

NoHttpResponseException!傳輸當場中斷,S3 上留下無人收拾的碎片,系統日誌完全沒留下任何退場紀錄!
重新打包並啟動具備 Graceful Shutdown 配置的容器docker compose up -d --build
再次使用Jmeter併發發射大檔案上傳。用docker ps 查看my-app微服務的conatiner id
在傳輸第 2 秒時,執行優雅關閉(docker stop 預設會發送 SIGTERM 訊號):docker stop <container_id>
可以觀察到docker console出現[tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
Jmeter客戶端 Console 可以看到穿插成功和失敗的reponse其中 HTTP/1.1 200 OK
Upload success: 888e8400-e29b-41d4-a716-446655448888
當 docker stop 下達時,微服務沒有終止,優雅地擋下了後續的新流量,並給予正在傳輸的連線 2.15 秒時間完成寫入 S3。客戶端順利拿到 200 OK,隨後進程平靜退場,達成 0 封包遺失!
Commencing graceful shutdown: 收到 SIGTERM 後,Spring Boot 立即啟動關機流程,停止接納新連線。sdk-async-response-0-1 到 0-6) 依然陸續傳輸成功,並印出 [PUT 成功]!Graceful shutdown complete ➔ Exit Code 143: 花了約 1.55 秒等所有既有任務完成後,系統印出 Graceful shutdown complete 並以 Exit Code 143 平靜退場。額外補充分享一下,在企業級微服務與 K8s 架構中,除了應用程式本身的 Graceful Shutdown,通常還會搭配以下工具
PreStop Hook 執行腳本,並先將 Readiness Probe 標記為 Unready(不健康)。今天的內容我們學到優雅停機是微服務走向 99.99% 高可用與零停機部署的必要入場券。商業價值方面,它讓我們在重啟與更新容器時,而是做到拒絕新客、清空舊客,不僅保住了最後一個非同步封包的完整性,捍衛了企業的商業 SLA 與用戶信任。
當單一容器學會了如何體面退場,我們也已經將單一容器調校到了極致——具備高吞吐、防衝擊、可觀測、與優雅退場能力。
但是,單一容器再怎麼完美,依然是一個單點主機。當實體斷電、當網路卡爆死、或是突發流量遠超單機 1.5 核極限時,我們的微服務依然無法自救。明天開始我們將進入下一個階段,告別單機 Docker,踏入雲端原生的分散式領域,手把手教大家撰寫 K8s YAML、部署本地 Minikube 叢集、設定 HPA 水平自動擴展、以及設定 Liveness & Readiness 雙探針,帶大家體驗什麼是高可用自我修復叢集!