1 概念
2 用案例說話
案例前:k8s 停機流程
k8s + springboot + nacos 案例
案例最佳化
3 再次最佳化
mq 和 定時任務
流量控制
4 小結
1 概念
優雅停機是什麽?網上說的優雅下線、無失真下線,都是一個意思。
優雅停機,通常是指在裝置、系統或應用程式中止運作前,先執行一定的流程或動作,以確保數據的安全、預防錯誤並保證系統的整體穩定。
一般來說,優雅停機可以參考以下步驟以實作:
備份數據 :立即將記憶體中的所有未保存的修改、緩存等數據保存到資料庫或磁盤中。
停止接收新的請求
處理未完成的請求
通知其他依賴元件
等待所有要素安全結束後,關閉系統
在具體實施時,不同的裝置、不同的系統、不同的套用,所需要的優雅停機步驟也不盡相同,甚至需要根據不同的場景來選擇不同的方法。
例如,在某些情況下,你可能需要讓使用者知道,系統即將關閉,並告訴他們應當保存所有的工作並結束系統;而在另一些情況下,你可能需要設計一種策略,能夠讓系統在無使用者介入的情況下,自動保存所有的狀態,並在下次啟動時恢復之。
但是,無論在哪種情況下,優雅停機的目標都是保護數據,避免錯誤,並盡量減少到訪使用者或使用者的不便。
上面的步驟,其實還缺了不少基礎的內容,比如,停止請求外,還要停止接收定時任務、停止接收mq訊息,等待他們的完成,這2項都是我們微服務中必不可缺的能力。
因此,我希望透過本文,能夠更清晰,更詳細的講解,在我已知的真實業務場景下,如何做優雅停機。
文中,很多內容不會講得太詳細,需要大家有一定的搜尋能力或者經驗!
2 用案例說話
隨著微服務的興起,運維方式由docker -> k8s 變化,優雅停機涉及到的點就越來越多!下面,我們用一個案例,說明優雅停機中的問題和問題解決方案。
案例前:k8s 停機流程
當程式設計師執行 kubectl delete pod 命令時,兩個過程開始:
網路規則即將生效:
Kube-apiserver 收到 pod 刪除請求,並將 pod 的狀態更新為 Extinating at Etcd;
終結點控制器從終結點物件中刪除 Pod 的 IP;
Kuber-proxy 根據 Endpoint 物件的更改更新 iptables 的規則,並且不再將流量路由到已刪除的 pod。
刪除容器:
Kube-apiserver 收到 pod 刪除請求,並將 pod 的狀態更新為 Extinating at Etcd;
Kubelet 清理節點處的容器相關資源,如儲存、網路;
添加 Prestop hook 勾點,等待流量不再發給pod;
Kubelet 將SIGTERM發送到容器;
如果容器在預設的 30 秒內沒有結束,Kubelet 將發送 SIGKILL 並強制其結束。
k8s + springboot + nacos 案例
PreStopHook 做了2件事情:
nacos反註冊
休眠35秒
透過號誌關閉springboot程式;
其中,k8s的
terminationGracePeriodSeconds
(寬限期)設定為35s。
問題
springBoot程式關閉時間只有2s, 那麽該程式就無法處理完一些執行緒任務、異步訊息、定時任務等。為什麽呢?
寬限期設定了35s,PreStop休眠了35s + 一個請求的時間,超過了寬限期,那麽 kubelet 就會給與 pod 增加一次性2s的寬限時間。Pod 的生命周期,2s不管程式是否正常結束,都會被Kill -9。
為什麽反註冊之後需要休眠35s?
這裏涉及到nacos服務發現原理,nacos服務變更響應時間:即時;ribbon 預設緩存重新整理時間30s;因此,一開始是設定30s的,發現還有feign請求失敗的情況,所以設定成了35s以解決這個問題!
nacos服務變更響應時間真的是即時嗎?
其實並不一定,nacos服務發現是透過http和udp實作的,udp是即時的,http最大等待時間是10s,但是,udp埠生產環境可能沒有開放!所以,案例中的nacos服務發現僅透過http定時輪詢實作。
案例最佳化
上面的案例可以最佳化的點
nacos 反註冊後休眠35s,是否可以減少;
terminationGracePeriodSeconds
設定多少合理?
最佳化點1
反註冊後休眠的35s時候受到nacos服務發現 + ribbon 緩存重新整理時間影響,正常應該是 服務發現時間 + 緩存重新整理時間 40s才能在極端情況下保證服務停機時,不會再有feign 請求進入。
如果想要縮短這個時間
啟用udp,這個需要和運維同學商量,否則10s等待少不了;
監聽nacos服務變更通知,發現服務下線後,及時重新整理ribbon緩存;
/**
* 訂閱 nacos 例項變更通知
* 手動重新整理 ribbon 服務例項緩存
* nacos client 1.4.6 【1.4.1有重大缺陷,要註意】
*/
@Component
@Slf4j
public class NacosInstancesChangeEventListener extends Subscriber<InstancesChangeEvent> {
@Resource
private SpringClientFactory springClientFactory;
@PostConstruct
public void registerToNotifyCenter(){
NotifyCenter.registerSubscriber(this);
}
@Override
public void onEvent(InstancesChangeEvent event) {
String service = event.getServiceName();
// service: DEFAULT_GROUP@@demo ribbonService: demo
String ribbonService = service.substring(service.indexOf("@@") + 2);
log.info("#### 接收到微服務nacos例項變更事件:{} ribbonServiceName: {}", event.getServiceName(), ribbonService);
ILoadBalancer loadBalancer = springClientFactory.getLoadBalancer(ribbonService);
if(loadBalancer != null){
((ZoneAwareLoadBalancer<?>) loadBalancer).updateListOfServers();
log.info("重新整理 ribbon 服務例項:{} 緩存成功", ribbonService);
}
}
@Override
public class<? extends com.alibaba.nacos.common.notify.Event> subscribeType() {
return InstancesChangeEvent. class;
}
/**
* nacos 1.4.4 ~ 1.4.6 需要加這個方法的實作, 2.1.2以後版本修復了該問題
* 多註冊中心時,變更事件沒有隔離,因此需要實作該方法來判斷事件是否需要處理
* @see <a href="https://github.com/alibaba/nacos/issues/8428">ISSUE #8428 - Nacos InstancesChange Event Scope</a>
* **/
@Override
public boolean scopeMatches(InstancesChangeEvent event) {
returntrue;
}
}
最佳化點2
terminationGracePeriodSeconds
的值應該略大於 PreStop耗時 + springBoot 停機時間,springBoot 停機時間是由程式業務決定的(mq訊息、定時任務、執行緒池任務、以及備份數據),網上的推薦做法是啟用springBoot的優雅停機功能,並實作自訂的關閉邏輯。
springBoot優雅停機的預設緩沖時間是30s,因此,
terminationGracePeriodSeconds
的時間個人建議10 + 30s即可。
經過最佳化後
使用 actuator shutdown 方案
有些網貼推薦使用
actuator shutdown
進行優雅停機,那麽看下其流程圖:
其實,真正的情況並非如上圖所示,因為呼叫shutdown後,springBoot就會進入優雅停機流程,但是這個流程沒有結束,然後就會被
kill -15
中斷,如果執行緒池沒有做好配置,執行緒池任務沒有結束,服務就會關閉。
// 沒有設定下面參數,在kill -15時,執行緒池沒有執行結束,會被強制關閉
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setAwaitTerminationSeconds(30);
3 再次最佳化
mq 和 定時任務
上面的方案中,提到nacos反註冊時,其他服務監聽反註冊事件,進行ribbon緩存重新整理,那麽,反註冊的服務(停機服務)自身,是否可以也監聽該事件呢?答案是可以的。
停機的服務監聽nacos反註冊事件,判斷是自己反註冊了,表示準備關機,那麽就可以停止對mq訊息的監聽,停止定時任務,這樣就比在優雅停機時,進行mq 和 定時任務的停止更完美。
流量控制
如果沒有使用k8s進行pod節點的流量控制,那麽大機率會使用 springCloud gateway作為服務閘道器,因此,gateway 服務也應該監聽nacos的反註冊事件,從而及時重新整理ribbon的緩存,關閉停機服務的流量。
4 小結
經過大量的資料參考、學習,最終得到的一份自己認為合格的優雅停機方案,裏面可能有較多的不專業表述,敬請諒解和指正,謝謝。
在本文的最後,還要說下,優雅停機最大的挑戰並不是來源於這個優雅停機流程,機械化的流程前人都幫忙躺過了,剩下的是業務服務自身的邏輯:
有沒有包含超過30s的業務邏輯,如執行超過30s的請求,定時任務、執行緒池任務或mq訊息;
服務關閉時,如何保存未完成的任務、數據,實作自訂的關閉邏輯;
介面邏輯是否做了冪等;
如喜歡本文,請點選右上角,把文章分享到朋友圈
如有想了解學習的技術點,請留言給若飛安排分享
因公眾號更改推播規則,請點「在看」並加「星標」 第一時間獲取精彩技術分享
·END·
相關閱讀:
作者:hj545king
來源:juejin.cn/post/7281486769290526757
版權申明:內容來源網路,僅供學習研究,版權歸原創者所有。如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝!
架構師
我們都是架構師!
關註 架構師(JiaGouX),添加「星標」
獲取每天技術幹貨,一起成為牛逼架構師
技術群請 加若飛: 1321113940 進架構師群
投稿、合作、版權等信箱: [email protected]