應(yīng)用不停機(jī)發(fā)布的思考與初識
應(yīng)用發(fā)布,簡單來說就是將已開發(fā)完成的系統(tǒng)功能部署到生產(chǎn)環(huán)境,并可正常對用戶提供服務(wù)。
傳統(tǒng)的應(yīng)用發(fā)布步驟一般采用“三步曲”:
第一步:停止應(yīng)用
第二步:更新應(yīng)用
第三步:啟動應(yīng)用
那你肯定會問,從停止應(yīng)用一直到啟動應(yīng)用期間,系統(tǒng)功能是不是無法正常使用?
沒錯。在應(yīng)用發(fā)布過程中,可能會出現(xiàn)頁面白屏、訪問超時等各種異常,而且一般會持續(xù)很久,所以發(fā)布時間基本上都集中在凌晨,講究點的可能就配上一句友好提示“系統(tǒng)正在維護(hù),請稍后訪問!”。
這種情況大部分都出現(xiàn)在傳統(tǒng)行業(yè),原因可能是覺得沒有必要,因為:
- 傳統(tǒng)行業(yè)的業(yè)務(wù)一般都集中的日間,也就是說凌晨基本沒有業(yè)務(wù),或者非重要業(yè)務(wù)。
- 就算凌晨無法使用這些功能,覺得也沒關(guān)系,第二天再來就是了,客戶又不會跑了。
但真的是這樣嗎?如今越來越多的傳統(tǒng)行業(yè),都在向互聯(lián)網(wǎng)服務(wù)模式轉(zhuǎn)型,其服務(wù)主要特點:“全天”不間斷為客戶提供“優(yōu)質(zhì)”的“線上”服務(wù),拆分一下關(guān)鍵詞:
- “全天”代表著任何時間
- “優(yōu)質(zhì)”代表著客戶體驗
- “線上”代表著線上自助
所以說,一個優(yōu)質(zhì)的客戶服務(wù)勢必會對服務(wù)可用性提出更高的要求,而傳統(tǒng)的停機(jī)發(fā)布不但會對客戶造成使用影響,還會變向?qū)ζ髽I(yè)造成非直接經(jīng)濟(jì)損失。
例如:客戶體驗下降導(dǎo)致的客戶流失
僅此而已?非也!作為曾經(jīng)也同樣是一名運維工程師的我來說,凌晨發(fā)布家常便飯,還時不時來一次長達(dá)8小時的“長途之旅”,身體就直接被掏空,加上第二天還在“補(bǔ)血”中,還會被各種騷擾電話轟炸,這簡直就是一場噩夢。
由此可見,應(yīng)用不停機(jī)發(fā)布的重要性,通過它可以幫助我們:一是在應(yīng)用發(fā)布過程中線上服務(wù)持續(xù)可用,提升服務(wù)可用性,二是可在白天或是任何時間發(fā)布,提升運維人員的機(jī)動性。
那么我們需要怎么做,才能實現(xiàn)應(yīng)用不停機(jī)發(fā)布呢,那接下來就讓我們進(jìn)入正題。
一
應(yīng)用不停機(jī)發(fā)布,從字面上很好理解,就是應(yīng)用發(fā)布過程中服務(wù)不中斷。
但仔細(xì)回想一下,原先應(yīng)用發(fā)布過程中,反正服務(wù)會中斷,那就在應(yīng)用發(fā)布完成后,通過網(wǎng)絡(luò)屏蔽外部流量的方式,先進(jìn)行核心或常用功能的內(nèi)部驗證,在確保沒有問題后,再解除網(wǎng)絡(luò)屏蔽,并對外提供服務(wù)。
那現(xiàn)在呢?應(yīng)用發(fā)布后直接對外?不需要進(jìn)行內(nèi)部或小流量驗證?想必每個人的答案都有所不同。對此,可以對應(yīng)用不停機(jī)發(fā)布能力,簡單定義三個成熟度。
成熟度一級:應(yīng)用發(fā)布過程服務(wù)不中斷。
成熟度二級:應(yīng)用發(fā)布過程服務(wù)不中斷,單應(yīng)用功能可先進(jìn)行內(nèi)部/小流量驗證。
成熟度三級:應(yīng)用發(fā)布過程服務(wù)不中斷,多應(yīng)用(聯(lián)動)功能可先進(jìn)行內(nèi)部/小流量驗證。
注:不同的成熟度對技術(shù)能力的要求會有所不同,建議通過逐步演進(jìn)的方式來提升成熟度,并在演進(jìn)過程中對不同的技術(shù)能力進(jìn)行驗證,從而不斷完善。
有人會說藍(lán)綠發(fā)布,或有人會說滾動發(fā)布,灰度發(fā)布就可以實現(xiàn)以上能力。但其實,這些答案并不準(zhǔn)確,或者說并不全面,它們雖有交集,但無法涵蓋。
既然提到發(fā)布模式,那我們先來簡單比較一下幾種發(fā)布模式的優(yōu)缺點。
注:大部分的技術(shù)文章里都會提到,可通過以上任何一種發(fā)布模式,做到用戶無感知的應(yīng)用發(fā)布,但在實際情況下,并不完全足夠,還需要通過一些輔助手段組合在一起才能實現(xiàn)。
結(jié)合以上發(fā)布模式的優(yōu)缺點,如果你的組織在基礎(chǔ)架構(gòu)技術(shù)能力上已經(jīng)非常成熟,那么灰度發(fā)布一定是最佳之選,但如果還處于初級階段,那可能并不是,而藍(lán)綠發(fā)布的資源投入較高,也不是一種非常好的選擇。
剩下的只有滾動發(fā)布,但滾動發(fā)布的發(fā)布策略會比較復(fù)雜,特別在容器環(huán)境下,同一應(yīng)用多個服務(wù)實例的滾動策略還需要單獨來實現(xiàn)。
所以,前期可以選擇一種折中的方式,即:單獨搭建一套預(yù)發(fā)驗證環(huán)境,應(yīng)用架構(gòu)需對齊生產(chǎn)環(huán)境,但容量方面可以最小化,一般一個應(yīng)用至少2個服務(wù)實例。
這樣,我們就可以在對生產(chǎn)環(huán)境做應(yīng)用發(fā)布前,事先對生產(chǎn)預(yù)驗證環(huán)境進(jìn)行應(yīng)用發(fā)布,發(fā)布后僅對內(nèi)部用戶可見,驗證通過后,再對生產(chǎn)環(huán)境采用全量應(yīng)用發(fā)布。
二
應(yīng)用不停機(jī)發(fā)布是一項綜合性能力,當(dāng)明確好一種發(fā)布模式后,就需要逐步識別會涉及到哪些技術(shù)組件,以及明確技術(shù)組件在整個解決方案中所擔(dān)任的職責(zé)邊界,從而使它們能夠相互協(xié)同工作。
如下列舉了一些主要的技術(shù)組件:
- 應(yīng)用管理平臺
主要負(fù)責(zé)控制整個應(yīng)用發(fā)布流程,以及集成并調(diào)度不同的技術(shù)組件,協(xié)同完成應(yīng)用不停機(jī)發(fā)布。
- 負(fù)載均衡(4層)
主要負(fù)責(zé)服務(wù)請求的流量接入,可根據(jù)所識別的流量特征,負(fù)載分發(fā)到不同的負(fù)載均衡(7層)。
- 負(fù)載均衡(7層)
主要負(fù)責(zé)服務(wù)請求的流量路由,可根據(jù)所識別的流量特征,路由分發(fā)到同一應(yīng)用的不同實例。
- 容器/虛擬機(jī)平臺
主要負(fù)責(zé)容器/虛擬機(jī)資源管理,包括容器/虛擬機(jī)的創(chuàng)建、更新、銷毀等。
- 域名解析系統(tǒng)
主要負(fù)責(zé)域名地址管理,包括域名的注冊、更新、解析等,以及提供用戶訪問應(yīng)用或應(yīng)用間訪問等尋址能力。
- 注冊中心
主要負(fù)責(zé)服務(wù)資源管理,包括服務(wù)的注冊、更新、注銷等,以及提供服務(wù)請求方發(fā)現(xiàn)服務(wù)提供方的能力。
在明確技術(shù)組件后,還需要對技術(shù)組件進(jìn)行合理的架構(gòu)規(guī)劃,以適配不同的網(wǎng)絡(luò)架構(gòu)要求。
本次主要將對負(fù)載均衡進(jìn)行特別說明,一方面它是負(fù)責(zé)處理流量的核心技術(shù)組件,另一方面網(wǎng)絡(luò)架構(gòu)的不同,對它的部署架構(gòu)影響可能也是最大的。
在傳統(tǒng)的網(wǎng)絡(luò)架構(gòu)環(huán)境中,出于對網(wǎng)絡(luò)安全或其他考慮因素,通常會劃分出多個不同的網(wǎng)絡(luò)區(qū)域,網(wǎng)絡(luò)區(qū)域間的訪問需要通過開設(shè)防火墻訪問策略才可以進(jìn)行互通。
但這種方式必然會對應(yīng)用服務(wù)間直接進(jìn)行點對點訪問的方式造成影響,主要原因是虛擬機(jī)或容器環(huán)境中,應(yīng)用的IP地址可能會發(fā)生變化,導(dǎo)致無法提前明確防火墻訪問策略。
所以,一般都會考慮使用負(fù)載均衡(代理模式)來解決這個問題。
建議前期優(yōu)先選擇方案三,雖然鏈路較長會小幅影響性能,但此部署方案相對較為成熟,一方面可以避免流量負(fù)載不均的問題,另一方面對于應(yīng)用的改造成本也會相對較低。
注:除網(wǎng)絡(luò)區(qū)域隔離會遇到這種情況外,在某些網(wǎng)絡(luò)架構(gòu)中,不同的容器集群間也同樣無法訪問,所以也同樣適用。
另外,除必要情況下,也應(yīng)盡量減少跨網(wǎng)絡(luò)區(qū)域或跨容器集群間的訪問,例如:優(yōu)先容器集群內(nèi)路由訪問,跨網(wǎng)絡(luò)區(qū)域頻繁交互的應(yīng)用服務(wù)建議遷移至同一網(wǎng)絡(luò)區(qū)域等。
負(fù)載均衡通??梢苑譃?層模式和7層模式,其中4層更關(guān)注流量負(fù)載,而7層更關(guān)注流量路由。
一般建議將負(fù)載均衡(4層)和負(fù)載均衡(7層)進(jìn)行分層部署,以充分發(fā)揮它們的強(qiáng)項。
建議前期優(yōu)先選擇方案二,雖然無法實現(xiàn)多容器集群間的全局流量調(diào)度,但對于當(dāng)前可觀測性和排障能力還不夠健全的組織,通過物理隔離降低運維難度也是一種不錯的選擇。
綜上架構(gòu)決策,最終的全局流量鏈路大致如下,默認(rèn)情況下,數(shù)據(jù)中心間流量隔離,即:單數(shù)據(jù)中心流量收斂,但可根據(jù)實際情況進(jìn)行選擇性放行。
三
應(yīng)用不停機(jī)發(fā)布的基礎(chǔ)是服務(wù)路由,在這里,我們可以把應(yīng)用服務(wù)分為兩種角色,服務(wù)請求方或服務(wù)提供方,而服務(wù)請求方通過不同的尋址方式,最終訪問到服務(wù)提供方的形式,可以稱之為服務(wù)路由。
服務(wù)路由可以分為南北向和東西向。
- 南北向:服務(wù)請求方—>負(fù)載均衡(4層)—>負(fù)載均衡(7層)—>服務(wù)提供方
- 東西向:服務(wù)請求方—>服務(wù)提供方
不難發(fā)現(xiàn),其主要區(qū)別就是,南北向服務(wù)請求方需借助負(fù)載均衡(代理模式)訪問服務(wù)提供方,而東西向服務(wù)請求方直接訪問服務(wù)提供方。
注:域名解析結(jié)果會緩存在本地,可緩解域名解析服務(wù)壓力,但緩存時間應(yīng)根據(jù)不同的服務(wù)水平進(jìn)行評估,否則將會延長解析地址切換及故障轉(zhuǎn)移時長。有條件的話建議采用httpdns來解決本地緩存問題。
但不管是南北向還是東西向,服務(wù)提供方在被訪問前,得讓服務(wù)請求方感知或可見,常見的方式主要有兩種,一種是不依賴注冊中心的,而另一種則是依賴注冊中心的。
如果當(dāng)前應(yīng)用架構(gòu)上暫未使用微服務(wù)框架,即:不依賴注冊中心,服務(wù)提供方可以手動或自動將服務(wù)在負(fù)載均衡上進(jìn)行服務(wù)注冊,服務(wù)請求方直接調(diào)用負(fù)載均衡。
如果當(dāng)前應(yīng)用架構(gòu)上已經(jīng)使用微服務(wù)框架,即:依賴注冊中心,服務(wù)提供方可以在注冊中心上進(jìn)行服務(wù)注冊,服務(wù)請求方在注冊中心進(jìn)行服務(wù)發(fā)現(xiàn),或者由負(fù)載均衡在注冊中心進(jìn)行服務(wù)發(fā)現(xiàn),服務(wù)請求方直接調(diào)用負(fù)載均衡。
注:在多注冊中心的場景下,可通過統(tǒng)一注冊中心完成異構(gòu)注冊中心的服務(wù)發(fā)現(xiàn)。
另外,我們還會對同一應(yīng)用的不同服務(wù)實例進(jìn)行分組,分組策略可根據(jù)不同的條件來決定,例如:不同環(huán)境、不同版本等。
假設(shè)根據(jù)不同環(huán)境(預(yù)生產(chǎn)環(huán)境和生產(chǎn)環(huán)境)這個條件,可以把部署在預(yù)發(fā)驗證環(huán)境的應(yīng)用服務(wù)分為預(yù)發(fā)組,把部署在生產(chǎn)環(huán)境的應(yīng)用服務(wù)分為線上組。
然后通過配置路由策略,下發(fā)到負(fù)載均衡或應(yīng)用服務(wù),就可以實現(xiàn)簡單的服務(wù)路由了。
例如:不同分組間默認(rèn)情況下不允許服務(wù)路由,但特殊場景下允許預(yù)發(fā)組服務(wù)路由至線上組。
注:若服務(wù)請求方是前端頁面或客戶端,也可以對前端流量也進(jìn)行分組,例如:根據(jù)網(wǎng)絡(luò)環(huán)境,將接入公司W(wǎng)I-FI網(wǎng)絡(luò)環(huán)境的流量識別成預(yù)發(fā)組。
四
應(yīng)用不停機(jī)發(fā)布的核心是應(yīng)用如何優(yōu)雅停止,光有服務(wù)路由可能還不夠,它雖然已經(jīng)解決了大部分問題,但離成功還差最后一步。
前面我們提到過應(yīng)用發(fā)布要做到用戶無感知,那如果應(yīng)用發(fā)布過程中出現(xiàn)瞬斷或短時間中斷,而用戶又正好在使用,那算不算用戶就感知到了呢?
不過,如果你覺得線上服務(wù)中斷5分鐘也可以忍受,那我只能呵呵了。但我相信大部分具備互聯(lián)網(wǎng)服務(wù)模式的頭部公司,別說發(fā)布一次服務(wù)停止5分鐘,可能就連5秒鐘也無法忍受。
那我們先來分析一下為什么會出現(xiàn)瞬斷或短時間中斷:
服務(wù)請求處理還沒完成,應(yīng)用就被強(qiáng)行停止(例如:運維必備大招之kill -9 進(jìn)程),導(dǎo)致處理中的請求無法正常返回。
服務(wù)提供方雖然已停止,但未通知服務(wù)請求方,服務(wù)請求方仍然繼續(xù)訪問已停止的服務(wù)提供方,導(dǎo)致出現(xiàn)異常。
針對以上兩種情況,還需要分南北向和東西向進(jìn)行討論。
在南北向,服務(wù)請求方與服務(wù)提供方間是通過負(fù)載均衡進(jìn)行服務(wù)路由,所以,在應(yīng)用發(fā)布時,需要在負(fù)載均衡上進(jìn)行一些特殊處理。
1)在負(fù)載均衡上,逐一對應(yīng)用服務(wù)實例進(jìn)行流量屏蔽,該屏蔽只會影響新的請求,等待服務(wù)請求處理完成后,再執(zhí)行如下操作:
- 容器:銷毀服務(wù)實例,創(chuàng)建新版本服務(wù)實例
- 虛擬機(jī):更新服務(wù)實例,更新后解除屏蔽
注:商業(yè)化的負(fù)載均衡一般都支持優(yōu)雅下線/上線。
2)因服務(wù)請求方的請求都會先經(jīng)過負(fù)載均衡,而負(fù)載均衡的成員節(jié)點只要確保至少有1個服務(wù)實例存在即可,該場景不需要通知,因此不適用。
在東西向,服務(wù)請求方與服務(wù)提供方間是直接進(jìn)行服務(wù)路由的,所以,在應(yīng)用發(fā)布時,需要分別在服務(wù)請求方和服務(wù)提供方進(jìn)行一些特殊處理,而這些處理方式受限于應(yīng)用框架,這里先介紹一種常用框架,即:Springboot+Eureka應(yīng)用框架
- 在服務(wù)提供方上實現(xiàn)Shutdown hook,即:等待服務(wù)請求處理完成后,再進(jìn)行應(yīng)用停止。
- 在服務(wù)提供方停止前,需通知所有服務(wù)請求方,并在完成通知后再進(jìn)行應(yīng)用停止。
其中第2點看上去似乎很簡單,但實際上會比較復(fù)雜,對此我做了一些降級,用于權(quán)衡收益價值和改造成本。
方式一:最多中斷5秒
step1:依賴spring-boot-starter-actuator組件,暴露/shutdown端點
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
step2:調(diào)整Eureka配置參數(shù),該調(diào)整會分別增加Eureka客戶端和服務(wù)端性能壓力
step3:容器銷毀服務(wù)實例或虛擬機(jī)更新服務(wù)實例前,請求如下地址進(jìn)行應(yīng)用服務(wù)實例停止
curl -X http://應(yīng)用服務(wù)地址/actuator/shutdown
方式二:不中斷(不通知)
step1:依賴spring-boot-starter-actuator組件,暴露/shutdown和/service-registry端點
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown,service-registry
step2:調(diào)整Eureka配置參數(shù),該調(diào)整會分別增加Eureka客戶端和服務(wù)端性能壓力
step3:容器銷毀服務(wù)實例或虛擬機(jī)更新服務(wù)實例前,請求如下地址進(jìn)行應(yīng)用服務(wù)實例下線
curl -X http://應(yīng)用服務(wù)地址/actuator/service-registry?status=DOWN
注:此時,服務(wù)提供方仍然可以提供服務(wù),但再次獲取服務(wù)實例的服務(wù)請求方不會再獲取該服務(wù)實例。
step4:服務(wù)請求方最多等待5秒后會更新服務(wù)列表,所以,在完成以上操作后,可以休眠5秒鐘,再請求如下地址停止應(yīng)用服務(wù)實例
curl -X http://應(yīng)用服務(wù)地址/actuator/shutdown
注:eureka.client.registry-fetch-interval-seconds+ribbon.ServerListRefreshInterval=5秒。
方式三:不中斷(通知)
step1:依賴spring-boot-starter-actuator組件,暴露/shutdown和/service-registry端點
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown,service-registry
step2:調(diào)整Eureka配置參數(shù),該調(diào)整會增加Eureka服務(wù)端性能壓力
step3:容器銷毀服務(wù)實例或虛擬機(jī)更新服務(wù)實例前,請求如下地址進(jìn)行應(yīng)用服務(wù)實例下線
curl -X http://應(yīng)用服務(wù)地址/actuator/service-registry?status=DOWN
注:此時,服務(wù)提供方仍然可以提供服務(wù),但再次獲取服務(wù)實例的服務(wù)請求方不會再獲取該服務(wù)實例。
step4:在注冊中心上記錄當(dāng)前正訂閱該服務(wù)實例的服務(wù)請求方列表,并根據(jù)列表通知它們立即重新獲取最新的服務(wù)實例,通知完成后再請求如下地址停止應(yīng)用服務(wù)實例
curl -X http://應(yīng)用服務(wù)地址/actuator/shutdown
以上三種方式,可以結(jié)合價值收益和改造成本進(jìn)行權(quán)衡,接受瞬斷的可以選擇方式一,而對技術(shù)有極致追求的可以選擇方式三,如果兩個都不是,那就選擇方式二吧。
注:每個應(yīng)用服務(wù)的Eureka配置參數(shù)并不一定能夠完全統(tǒng)一,這樣可能就會造成大量配置管理成本的增加,但如果可以統(tǒng)一,那方式二還是不錯的選擇。
五
在具備以上條件能力后,應(yīng)用發(fā)布不停機(jī)的基本框架已成型,但這樣應(yīng)用發(fā)布就能實現(xiàn)不停機(jī)了?
那有這么簡單,我們在開發(fā)上還需要遵循一些規(guī)范,但符合這些規(guī)范的話,可能會增加我們的一些開發(fā)成本。
因此,并不是說每次應(yīng)用發(fā)布都強(qiáng)行需要實現(xiàn)不停機(jī)發(fā)布,而是應(yīng)該進(jìn)行合理的取舍。不過,一個好的系統(tǒng)設(shè)計必然能做到既能遵循開發(fā)規(guī)范,也不會增加太多開發(fā)成本。
如下列舉了一些常用的開發(fā)規(guī)范,實際情況可按需調(diào)整,其目的是為了不管是接口更新還是數(shù)據(jù)庫更新等,都應(yīng)盡量做到向上兼容。
1)接口
- 允許新增字段,必填字段需要設(shè)置缺省值;
- 允許原有字段擴(kuò)展長度或新增字典值;
- 不允許修改原有字段的語義及格式;
- 不允許刪除原有字段;
- 無法兼容時,應(yīng)新增接口;
- 接口下線前,需確保無調(diào)用方。
2)數(shù)據(jù)庫
- 允許新增字段,必填字段需要設(shè)置缺省值;
- 允許原有字段擴(kuò)展長度或新增字典值;
- 不允許修改原有字段的語義及格式;
- 不允許刪除原有字段;
- 無法兼容時,應(yīng)新增表;
- 新老表并存期間,數(shù)據(jù)統(tǒng)計需聚合處理;
- 老表下線前,需進(jìn)行數(shù)據(jù)遷移。
3)消息
- 優(yōu)先考慮新老格式兼容;
- 無法兼容時,生產(chǎn)者和消費者可共同約定新的主題;
- 若生產(chǎn)者無法約定新的主題,消費者可增加消息分發(fā)層進(jìn)行主題重命名。
4)緩存
優(yōu)先考慮新老格式兼容;
寫在最后
感謝你可以很耐心的讀到這里,整篇文章主要圍繞應(yīng)用不停機(jī)發(fā)布進(jìn)行了思考,從為什么要做,能帶來什么價值,一直到應(yīng)該怎么做,要關(guān)心哪些方面,進(jìn)行了簡要說明,主要目的是為了能讓大家,對應(yīng)用不停機(jī)發(fā)布有一個大概的框架認(rèn)識。
實現(xiàn)應(yīng)用不停機(jī)發(fā)布的手段,也并非僅有文章中說的那些方式,涉及到的技術(shù)組件可能也不止這些,但其解決思路基本都差不多,具體的技術(shù)實現(xiàn)方式,可以結(jié)合自身的架構(gòu)環(huán)境再進(jìn)行適配和調(diào)整。