一、微服務應用上/下線發(fā)布過程中存在的問題
在應用上下線發(fā)布過程中,如何做到流量的無損上/下線,是一個系統(tǒng)能保證 SLA 的關鍵。如果應用上下線不平滑,就會出現(xiàn)短時間的服務調(diào)用報錯,比如連接被拒絕、請求超時、沒有實例和請求異常等問題。
1.1 上線過程中的問題
在應用上線發(fā)布過程中,由于過早暴露服務,實例可能仍處在 JVMJIT 編譯或者使用的中間件還在加載,若此時大量流量進入,可能會瞬間壓垮新起的服務實例。我們在實際場景中,曾經(jīng)遇到 provider 服務啟動后,但是數(shù)據(jù)庫連接出現(xiàn)異常,未做好啟動前的資源準備,導致該 provider 服務在注冊中心暴露后 DB 異常還未修復,無法正常提供被 consumer 調(diào)用的能力,導致大量請求異常返回。如下圖日志所示,應用初始化時,DB 連接失?。ㄔ摲諏?DB 是弱依賴)。
1.2 下線過程中的問題
在應用下線過程中,服務消費者感知服務提供者下線有延遲,在一段時間內(nèi),被路由到已下線服務提供者實例的請求都拋連接被拒絕異常。其次服務實例在接收到 SIGKILL 信號時,會立即關閉,但是這時候可能在請求隊列中存在一部分請求還在處理,如果立即關閉這些請求都會損失掉。實際應用中,我們在環(huán)境上部署了 provider 的唯一一個實例,該服務被 consumer 調(diào)用,然后再執(zhí)行 kill-9強殺應用 provider 的唯一實例后,服務進程實際上已經(jīng)被終止,但是服務的注冊信息還會在注冊中心(該場景使用的是 ServiceComb)保留一段時間,未及時清除,如下圖所示。若此時消費者服務 consumer 調(diào)到該實例會報連接拒絕錯誤。因為消費者 consumer 服務還能發(fā)現(xiàn)該實例,獲取其 IP 和端口嘗試去調(diào)用,但是該 provider 服務實例其實已經(jīng)被銷毀了。
二、如何處理應用上/下線問題
那么有哪些優(yōu)化措施,可以減少應用上/下線中流量的損失?
2.1 處理應用上線問題
應用上線發(fā)布主要問題是:其中一個原因是注冊太早,過早的暴露了服務;另一個原因是一些應用初始化緩慢,若遇到大量流量,應用容易宕機??梢圆扇∫韵聝?yōu)化措施:
1.延遲注冊:微服務應用可以采用延遲注冊的方式,即在應用啟動之后一定時間再進行注冊。這樣可以確保應用完全就緒后再注冊,避免了服務未就緒就被外部訪問的情況。
2.健康檢查:微服務應用可以實現(xiàn)健康檢查接口,通過該接口可以檢查服務是否就緒。注冊中心可以通過定期調(diào)用該接口來判斷服務是否可以對外提供服務,從而避免了服務未就緒就被外部訪問的情況。
3.預熱:對新實例進行預熱,而不是突然將所有流量轉(zhuǎn)移到新實例上,從而避免新實例遇到大量流量,應用容易宕機的情況。
4.啟動優(yōu)化:對于整個服務啟動的過程,可以進行一些優(yōu)化措施,比如減少不必要的依賴、調(diào)整啟動順序等,從而加快服務啟動速度。
2.2 應用合理的上線過程
合理的應用上線大致分為這樣一個過程:當應用啟動后,通過設置延遲注冊時間(服務對外暴露的時間)確保應用多久后可提供服務,其次可依賴平臺檢查服務的就緒狀態(tài)(比如 K8S 的就緒探針)確保服務對外提供服務為就緒狀態(tài),然后通過預熱對剛啟動應用進行保護,確保流量慢慢進入剛啟動的應用,最后流量逐漸增到正常情況。
2.3 處理應用下線問題
應用下線過程最主要問題是:消費者應用無法及時感知到注冊中心列表的刷新,導致可能還有新流量訪問下線應用。可以采取以下優(yōu)化措施:
1.減少注冊中心緩存時間:將注冊中心中服務列表的緩存時間縮短,可以使消費者應用更快地獲取到服務列表的最新信息。這樣可以減少因服務列表緩存而導致的訪問下線應用的流量。
2.實時性優(yōu)化:在服務消費者和注冊中心之間使用長連接、實時通知等機制,從而能夠?qū)崟r獲取注冊中心中服務列表的變化。
3.實現(xiàn)熔斷機制:在消費者應用中實現(xiàn)熔斷機制,當某個服務實例出現(xiàn)故障或不可用時,可以快速切換到其他可用的服務實例。這樣可以避免將流量發(fā)送到已下線的應用程序上,并確保消費者應用的可用性。
2.4 應用合理的下線過程
合理的應用下線大致分為這樣一個過程:當應用接受到外部的關閉(停止服務)請求后,不能在接收新的業(yè)務請求,但是會存在一些正在處理的業(yè)務請求,需等這些請求處理完后再銷毀應用使用的資源,最后就可以通知主進程退出。
三、應用下線注意點
針對應用下線在虛機場景和容器場景需要關注一些注意點。
3.1 虛機場景
當我們要關閉虛擬機應用時,我們一般會使用 ps-ef|grepxxx 查找到進程 ID,然后再執(zhí)行 kill-9PID 操作。
kill命令使用科普:
1.kill-9,系統(tǒng)會發(fā)出 SIGKILL(9)信號,由操作系統(tǒng)內(nèi)核完成殺進程操作,該信號不允許忽略和阻塞,應用程序會立即終止(強制殺死)。
2.kill-15,默認使用信號,系統(tǒng)向應用發(fā)送 SIGTERM(15)信號,給目標進程一個清理善后工作的機會是一種優(yōu)雅終止進程的方式,告訴進程需要停止運行并開始清理資源。
因為 kill-9PID 會強制殺死應用,以合理的應用下線流程看,應需處理完相關舊業(yè)務請求,清理相關資源后再退出進程,所以當要關閉虛擬機應用時,請執(zhí)行 killPID——以優(yōu)雅的方式停止運行。
3.2 容器場景
Kubernetes 目前是業(yè)界容器編排領域的事實標準,業(yè)界一般默認都是用 K8S 來管理容器。K8S 提供了 Pod 優(yōu)雅退出機制,允許 Pod 在退出前完成一些清理工作。preStop 會先執(zhí)行完,然后 K8S 才會給 Pod 發(fā)送 TERM 信號。在容器場景利用 K8S 提供的 preStop 機制,配合延遲下線 API 使用,這樣就能保證流量的無損下線。
...
spec:
-name:lifecycle-demo-container
image:nginx
lifecycle:
preStop:
exec:
command:["/bin/sh","-c","todoxxx;dosleep30;done"]
...
(1)為什么容器應用(K8S 環(huán)境)要配置 preStop?首先要介紹一下 Pod 的終止過程。
參考:https://kubernetes.renkeju.com/chapter_4/4.5.5.pod_termination_process.html
1.用戶發(fā)送刪除Pod對象的命令。
2.API服務器中的Pod對象會隨著時間的推移而更新,在寬限期內(nèi)(默認為 30 秒),Pod 被視為“dead”。
3.將Pod標記為“Terminating”狀態(tài)。
4.(與第 3 步同時運行)kubelet在監(jiān)控到Pod對象轉(zhuǎn)為“Terminating”狀態(tài)的同時啟動Pod關閉程序。
5.(與第 3 步同時運行)端點控制器監(jiān)控到Pod對象的關閉行為時將其從所有匹配到此端點的Service資源的端點列表中移除。
6.Pod對象中的容器進程收到TERM信號。
7.如果當前當前Pod對象定義了preStop鉤子處理器,則在其標記為“Terminating”后即會以同步的方式啟動執(zhí)行;如若寬限期結束后,preStop仍未執(zhí)行結束,則第 2 步會被重新執(zhí)行并額外獲取一個時長為 2 秒的小寬限期。
8.寬限期結束后,若存在任何一個仍在運行的進程,那么Pod對象即會收到SIGKILL信號。
9.kubelet請求APIServer將此Pod資源的寬限期設置為 0 從而完成刪除操作,它變得對用戶不在可見。
默認情況下,所有刪除操作的寬限期都是 30 秒,不過,kubectldelete命令可以使用“--grace-period=”選項自定義其時長,若使用 0 值則表示直接強制刪除指定的資源,不過,此時需要同時為命令使用“--force”選項。
從上述 Pod 終止過程的時序圖可知,關閉 Pod 流程(關注紅色框),給 Pod 內(nèi)的進程發(fā)送 TERM 信號(即 kill,kill-15),如果配置了 preStop 鉤子也會同時處理,最后寬限期結束后,若存在任何一個仍在運行的進程,那么 Pod 對象即會收到 SIGKILL(kill-9)信號。
(2)存在這樣一種情況 Pod 中的業(yè)務進程接受不到 SIGTERM 信號
存在這樣一種情況 Pod 中的業(yè)務進程接受不到 SIGTERM 信號(而且沒有配置 preStop 鉤子),等待一段時間業(yè)務進程直接被 SIGKILL 強制殺死了。
為什么業(yè)務進程接受不到 SIGTERM 信號?
通常都是因為容器啟動入口使用了shell,比如使用了類似/bin/sh-cmy-app或/docker-entrypoint.sh這樣的ENTRYPOINT或CMD,這就可能就會導致容器內(nèi)的業(yè)務進程收不到 SIGTERM 信號,原因是:
1.容器主進程是 shell,業(yè)務進程是在 shell 中啟動的,成為了 shell 進程的子進程。
2.shell進程默認不會處理SIGTERM信號,自己不會退出,也不會將信號傳遞給子進程,導致業(yè)務進程不會觸發(fā)停止邏輯。
3.當?shù)鹊終8S優(yōu)雅停止超時時間(terminationGracePeriodSeconds,默認 30s),發(fā)送 SIGKILL 強制殺死 shell 及其子進程。
(3)如何解決上述 Pod 中的業(yè)務進程接收不到 SIGTERM 信號問題
1.配置 preStop 鉤子(K8S 場景),處理退出前完成一些清理工作,比如使用無損上下線插件的應用服務需在停止前通知實例進行下線。
2.如果可以的話,盡量不使用shell啟動業(yè)務進程。
3.如果一定要通過shell啟動,比如在啟動前需要用shell進程一些判斷和處理,或者需要啟動多個進程,那么就需要在shell中傳遞下SIGTERM信號了。
所以容器應用(K8S 環(huán)境)要配置 preStop,在停止前通知實例進行下線,加了一層防護,保證 Pod 中的業(yè)務能優(yōu)雅的結束。
四、Sermant 如何解決應用上/下線問題
針對應用上下線發(fā)布過程中的問題,Sermant 插件提供預熱和延遲下線機制,為應用提供無損上下線的能力。預熱是無損上線的核心機制,延遲下線是無損下線的核心機制,而且為了無損上線,還做了延遲注冊機制。
4.1 上線問題的解決方式
延遲注冊:若服務還未完全初始化就已經(jīng)注冊到注冊中心提供給消費者調(diào)用,很有可能因資源為加載完成導致請求報錯。可以通過設置延遲注冊,讓服務充分初始化后再注冊到注冊中心對外提供服務。
預熱:是基于客戶端實現(xiàn)的,當流量進入時,Sermant 會動態(tài)調(diào)整流量,根據(jù)服務的預熱配置,對流量進行動態(tài)分配。對于開啟服務預熱的實例,在剛啟動時,相對于其他已啟動的實例,分配的流量會更少,流量將以曲線方式隨時間推移增加直至與其他實例近乎持平。目的是采用少流量對服務實例進行初始化,防止服務崩潰。
4.2 下線問題的解決方式
上圖描述了 Sermant 是如何解決服務下線問題的:
0.微服務應用 consumerA、providerA、consumerB、providerB 攜帶 Sermant 啟動,并將相關 ip:port 等信息注冊到注冊中心;
1.微服務應用 consumerA 可以正常調(diào)用 providerA 和 providerB;
2.若要重啟 providerA,providerA 會標記自身將下線(通知注冊中心將下線),并開始統(tǒng)計請求確保當前請求已全部處理完成;
3.providerA 會通知其上游應用其自身的下線信息;
4.consumerA 接受到 providerA 下線信息后,將其從緩存實例列表移除;
5.providerA 在處理完當前的所有請求后,即可重啟。
總的來說,Sermant 對于服務下線的機制概括為:
延遲下線:即對下線的實例提供保護,插件基于下線實時通知+刷新緩存的機制快速更新上游的實例緩存,同時基于流量統(tǒng)計的方式,確保即將下線的實例盡可能的將流量處理完成,最大程度避免流量丟失。提供了延遲下線 API,方便在 K8S 環(huán)境中配置 preStop。
流量統(tǒng)計:為確保當前請求已全部處理完成,在服務下線時,Sermant 會嘗試等待 30s(可配置),定時統(tǒng)計和判斷當前實例請求是否均處理完成,處理完成后最終下線。
五、總結
Sermant 插件為微服務應用提供無損上下線的能力,若要下線應用,針對虛擬場景,請使用 killPID;針對容器場景(K8S 環(huán)境),請配置 preStop 鉤子。
Sermant作為專注于服務治理領域的字節(jié)碼增強框架,致力于提供高性能、可擴展、易接入、功能豐富的服務治理體驗,并會在每個版本中做好性能、功能、體驗的看護。
編輯:黃飛
-
容器
+關注
關注
0文章
492瀏覽量
22027 -
華為云
+關注
關注
3文章
2391瀏覽量
17247
發(fā)布評論請先 登錄
相關推薦
評論