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

微服務(wù)與容器技術(shù)實(shí)踐

微服務(wù)優(yōu)勢_微服務(wù)架構(gòu)的好處與不足
什么是微服務(wù)和容器?微服務(wù)和容器的作用是什么

什么是微服務(wù)架構(gòu)_微服務(wù)架構(gòu)的優(yōu)缺點(diǎn)及應(yīng)用

微服務(wù)使用失敗的原因有什么
微服務(wù)架構(gòu)中的服務(wù)之間如何互相調(diào)用呢?
springcloud微服務(wù)架構(gòu)
docker微服務(wù)架構(gòu)實(shí)戰(zhàn)
設(shè)計(jì)微服務(wù)架構(gòu)的原則

評論