SpringCloud 優雅下線+灰度釋出
前言
在生產環境中,如何保證在服務升級的時候,不影響使用者的體驗,這個是一個非常重要的問題。如果在我們升級服務的時候,會造成一段時間內的服務不可用,這就是不夠優雅的。那什麼是優雅的呢?主要就是指在服務升級的時候,不中斷整個服務,讓使用者無感知,進而不會影響使用者的體驗,這就是優雅的。
實際上,優雅下線是目標,而不是手段,它是一個相對的概念,例如kill PID
和kill -9 PID
都是暴力殺死服務,相對於kill -9 PID
來說,kill PID
就是優雅的。但如果單獨拿kill PID
出來說,我們能說它是優雅的下線策略嗎?肯定不是啊,就是這個道理。
因此,本文講述的優雅下線僅能稱之為“相對的優雅下線”,但相對於暴力的殺死服務,已經足夠優雅了。常見的優雅解決方案,主要包括優雅下線和灰度釋出。而實際上,灰度釋出的範圍就已經包含優雅下線了。
最後,在本文中,我們主要講述基於 Spring Cloud 和 Euraka 的優雅下線以及灰度釋出。
優雅下線
常見下線模式
方式一:kill PID
使用方式:kill java程序ID
該方式藉助的是 Spring Boot 應用的 Shutdown hook
,應用本身的下線也是優雅的,但如果你的服務發現元件使用的是 Eureka,那麼預設最長會有 90 秒的延遲,其他應用才會感知到該服務下線,這意味著:該例項下線後的 90 秒內,其他服務仍然可能呼叫到這個已下線的例項。因此,該方式是不夠優雅的 。
方式二:/shutdown端點
Spring Boot 提供了/shutdown
端點,可以藉助它實現優雅停機。
使用方式:在想下線應用的application.yml中新增如下配置,從而啟用並暴露/shutdown
端點:
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
傳送 POST 請求到/shutdown端點
curl -X http://你想停止的服務地址/actuator/shutdown
該方式本質和方式一是一樣的,也是藉助 Spring Boot 應用的 Shutdown hook 去實現的。
方式三:/pause端點
Spring Boot 應用提供了/pause
端點,利用該端點可實現優雅下線。
使用方式:在想下線應用的application.yml中新增配置,從而啟用並暴露/pause
端點:
management:
endpoint:
# 啟用pause端點
pause:
enabled: true
# 啟用restart端點,之所以要啟用restart端點,是因為pause端點的啟用依賴restart端點的啟用
restart:
enabled: true
endpoints:
web:
exposure:
include: pause,restart
傳送 POST 請求到/actuator/pause
端點:
curl -X POST http://你想停止的服務例項地址/actuator/pause
執行後的效果類似下圖:
如圖所示,該應用在 Eureka Server 上的狀已被標記為DOWN,但是應用本身其實依然是可以正常對外服務的。在 Spring Cloud 中,Ribbon 做負載均衡時,只會負載到標記為UP的例項上。
利用這兩點,你可以:先用/pause
端點,將要下線的應用標記為DOWN,但不去真正停止應用;然後過一定的時間(例如 90 秒,或者自己做個監控,看當前例項的流量變成 0 後)再去停止應用,例如kill
應用。
缺點 & 侷限
方式四:/service-registry端點
使用方式:在想下線應用的application.yml中新增配置,從而暴露/service-registry
端點:
management:
endpoints:
web:
exposure:
include: service-registry
傳送 POST 請求到/actuator/service-registry
端點:
curl -X "POST" "http://localhost:8000/actuator/service-registry?status=DOWN" \
-H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
實行後的效果類似如下圖:
優雅下線模式
在上文中,我們講述了四種常見的下線方式,對比來看,方式四 是一種比較優雅的下線方式。
在實際專案中,我們可以先使用/service-registry
端點,將服務標記為DOWN,然後監控服務的流量,當流量為 0 時,即可升級該服務。當然,這裡假設我們部署了多個服務例項,當一個服務例項DOWN掉之後,其他服務例項仍然是可以提供服務的,如果就部署一臺服務的話,那麼討論優不優雅就沒那麼重要了。
除了上述的下線方式之外,還有一種利用EurekaAutoServiceRegistration
物件達到優雅下線的目標。
- 執行
eurekaAutoServiceRegistration.start()
方法時,當前服務向 Eureka 註冊中心註冊服務; - 執行
eurekaAutoServiceRegistration.stop()
方法時,當前服務會向 Eureka 註冊中心進行反註冊,註冊中心收到請求後,會將此服務從註冊列表中刪除。
示例程式碼如下:
@RestController
@RequestMapping(value = "/graceful/registry-service")
public class GracefulOffline {
@Autowired
private EurekaAutoServiceRegistration eurekaAutoServiceRegistration;
@RequestMapping("/online")
public String online() {
this.eurekaAutoServiceRegistration.start();
return "execute online method, online success.";
}
@RequestMapping("/offline")
public String offline() {
this.eurekaAutoServiceRegistration.stop();
return "execute offline method, offline success.";
}
}
到這裡,我們已經介紹了兩種相對優雅的下線方式了。具體如何操作,我們可以根據實際上情況進行包裝,或者利用自動化的指令碼來實現更加優雅的下線方式。
灰度釋出
藍綠部署
藍綠部署,英文名為 Blue Green Deployment,是一種可以保證系統在不間斷提供服務的情況下上線的部署方式。
如何保證系統不間斷提供服務呢?那就是同時部署兩個叢集,但僅對外提供一個叢集的服務,當需要升級時,切換叢集進行升級。藍綠部署無需停機,並且風險較小。其大致步驟為:
- 部署叢集 1 的應用(初始狀態),將所有外部請求的流量都打到這個叢集上
- 部署叢集 2 的應用,叢集 2 的程式碼與叢集 1 不同,如新功能或者 Bug 修復等
- 將流量從叢集 1 切換到叢集 2
- 如叢集 2 測試正常,就刪除叢集 1 正在使用的資源(例如例項),使用叢集 2 對外提供服務
因為在使用藍綠部署的方式時,我們需要控制流量,所以我們需要藉助路由服務,如 Nginx 等。
滾動部署
滾動部署,英文名為 Rolling Update,同樣是一種可以保證系統在不間斷提供服務的情況下上線的部署方式。和藍綠部署不同的是,滾動部署對外提供服務的版本並不是非此即彼,而是在更細的粒度下平滑完成版本的升級。
如何做到細粒度平滑升級版本呢?滾動部署只需要一個叢集,叢集下的不同節點可以獨立進行版本升級。比如在一個 12 節點的叢集中,我們每次升級 4 個節點,並將升級後的節點重新投入使用,周而復始,直到叢集中所有的節點都更新為新版本。
這種部署方式相對於藍綠部署,更加節約資源,因為它不需要執行兩個叢集。但這種方式也有很多缺點,例如:
- 沒有一個確定 OK 的環境。使用藍綠部署,我們能夠清晰地知道老版本是 OK 的,而使用滾動釋出,我們無法確定。
- 修改了現有的環境。
- 如果需要回滾,很困難。舉個例子,在某一次釋出中,我們需要更新 100 個例項,每次更新 10 個例項,每次部署需要 5 分鐘。當滾動釋出到第 80 個例項時,發現了問題,需要回滾。這時,我們估計就要瘋了。
- 有的時候,我們還可能對系統進行動態伸縮,如果部署期間,系統自動擴容/縮容了,我們還需判斷到底哪個節點使用的是哪個程式碼。儘管有一些自動化的運維工具,但是依然令人心驚膽戰。
並不是說滾動釋出不好,滾動釋出也有它非常合適的場景。
金絲雀部署
金絲雀部署又稱灰度部署(或者,灰度釋出),英文名為 Canary Deployment,是指在黑與白之間,能夠平滑過渡的一種釋出方式。
金絲雀的名稱來源於「礦井中的金絲雀」,早在 17 世紀,英國礦井工人發現,金絲雀對瓦斯這種氣體十分敏感,空氣中哪怕有極其微量的瓦斯,金絲雀也會停止歌唱;而當瓦斯含量超過一定限度時,雖然魯鈍的人類毫無察覺,金絲雀卻早已毒發身亡。當時在採礦裝置相對簡陋的條件下,工人們每次下井都會帶上一隻金絲雀作為“瓦斯檢測指標”,以便在危險狀況下緊急撤離。
我們來看一下金絲雀部署的步驟:
- 準備好部署各個階段的工件,包括:構建工件,測試指令碼,配置檔案和部署清單檔案
- 從負載均衡列表中移除掉“金絲雀”伺服器
- 升級“金絲雀”應用(切斷原有流量並進行部署)
- 對應用進行自動化測試
- 將“金絲雀”伺服器重新新增到負載均衡列表中(連通性和健康檢查)
- 如果“金絲雀”線上使用測試成功,升級剩餘的其他伺服器(否則就回滾)
在金絲雀部署中,常常按照使用者量設定路由權重,例如 90% 的使用者維持使用老版本,10% 的使用者嚐鮮新版本。不同版本應用共存,經常與 A/B 測試一起使用,用於測試選擇多種方案。
金絲雀部署比較典型的例子,就是我們在使用某個應用的時候,該應用邀請我們進行“內測”或者“新版本體驗”,如果我們同意了,那麼我們就成了金絲雀。