springboot優雅停機
概述
線上系統發佈重啓過程中保證業務的連續性,讓服務的客戶端無感知。實現這種目標的停機我們稱之爲優雅停機(Graceful Shutdown),這樣的發佈部署叫平滑發佈(Gentle Deploment).
線上重啓面臨的問題
系統間調用模型
常見問題
- 重啓tomcat將導致處理中的請求丟失
- 使用kill -15重啓tomcat,如果服務一直沒有成功關閉,將導致不斷有新的請求進入並被掛起,最終失敗.
解決方案思路
- 要做到應用容器停止不影響正在執行的業務.
- 負載均衡組件能自動感知服務節點下線和上線,新請求不再分發到舊服務.
- 對無法處理的請求嘗試重試補償.
優雅停機相關知識
Linux中斷
在Linux/unix 下,中止一個Java進程有兩種方式,一種是 kill -9 pid,一種是 kill -15 pill(默認)。
- SIGNKILL(9) 的效果是立即殺死進程。 該信號不能被阻塞, 處理和忽略。
- SIGNTERM(15) 的效果是正常退出進程,退出前可以被阻塞或回調處理。並且它是 Linux 缺省的程序中斷信號。
ShutdownHook
JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以註冊一個JVM關閉的鉤子,它是一個回調函數.在一下幾種場景中被調用:
- 程序正常退出
- 使用System.exit()
- 終端使用Ctrl+C觸發的中斷
- 系統關閉
- OutOfMemory宕機
- 使用Kill pid命令幹掉進程
SpringCloud對優雅停機的處理機制
SpringBoot ApplicationContext生命週期
SpringBoot Application在啓動時註冊了ShutdownHook,在doClose()中聲明瞭在ShutdownHook對DisposableBean進行關閉.
org.springframework.context.support.AbstractApplicationContext.registerShutdownHook()
org.springframework.context.support.AbstractApplicationContext.doClose()
Spring Bean生命週期
Spring框架中在bean初始化和銷燬時候執行某個方法的三種實現方式。
- 通過註解@PostConastruct 和 @PreDestroy來實現Bean初始化執行和銷燬時候執行方法;
- 通過實現接口InitializingBean ,DisposableBean來實現Bean初始化執行和銷燬時候執行方法;
- 通過xml配置文件中bean的init-method="" destroy-method=""來實現Bean初始化執行和銷燬時候執行方法;
服務註冊生命週期
ServiceRegistry聲明瞭spring cloud對服務服務註冊的生命週期.
通過AbstractAutoServiceRegistration.destroy()聲明瞭Application關閉時的回調動作.
@PreDestroy
AbstractAutoServiceRegistration.destroy()
EurekaClientAutoConfiguration這個自動配置類做了相應的工作。
Ribbon自動重試
Ribbon針對以下異常自動重試(HttpClientLoadBalancerErrorHandler.retriable )
- ConnectException.class,
- SocketTimeoutException.class
- ConnectTimeoutException.class,
- NoHttpResponseException.class,
- ConnectionPoolTimeoutException.class,
- ConnectionClosedException.class,
- HttpHostConnectException.class);
服務發現的心跳檢測
ConsulCatalogWatch#catalogServicesWatch()聲明瞭每隔一段時間會去註冊中心檢測服務狀態,狀態異常的服務節點將在負載均衡時排除掉.
Ribbon負載均衡
Ribbon的負載均衡體現在selectServer時.
tomcat的響應機制
- tomcat的Connections和Thread不是直接對於的關係,當線程阻塞或者線程池關閉時,依然可以建立連接.
- 當線程池正常,線程不夠時,如果沒有達到最大線程數,將不斷新建線程.
Nginx的負載均衡策略
- 當nginx發現upstream無法連接時,將不再分配請求到該節點
- 如果可以建立連接,將基於負載均衡策略分發
傳統架構的優雅停機方案
使用Nginx做tomcat的負載均衡,使用JDK自帶的HttpURLConnection.
方案
- 使用kill -15,等待30S,使用kill -9
- 儘量保證所有請求在3S內處理完成
- 接口調用方要設置超時參數,超時後對異常做處理
- 接口調用方有選擇的通過http重試或者整體任務重試實現完整性
微服務架構的優雅停機方案
使用Feign+Ribbon作爲客戶端和負載均衡.
方案
- 使用kill -15,等待10S,使用kill -9
- 儘量保證所有請求在1S內處理完成
- 接口調用方要設置超時參數,超時後對異常做處理
- 縮短服務發現健康檢查間隔,建議配置在1S以內(默認1S).
參考資料