服務優雅下線,沒你想的那麼簡單?

架構擺渡人發表於2022-01-16

大家好,我是架構擺渡人。這是實踐經驗系列的第八篇文章,這個系列會給大家分享很多在實際工作中有用的經驗,如果有收穫,還請分享給更多的朋友。

服務部署,是一個避免不了的問題。按正常迭代的速度一般兩週會發一個版本,此時就需要部署新的程式碼。釋出方式,我相信主流的都是用滾動釋出,因為這樣的成本是最低的,機器數量是固定的,一臺臺機器輪流釋出。

但是我們總會在釋出過程中碰到一些報錯資訊,那是因為請求還沒結束,某些元件已經強制停止了,比如我們的資料來源,比如非同步任務還沒處理完。

那麼如何解決這個問題呢?那就是服務優雅下線,估計大家都聽過這個詞,但我不知道有多少做到了隨時釋出都不影響功能的正常使用。

優雅下線涉及點

外部請求必須處理完

服務時時刻刻都在處理請求,一旦收到要停止的命令,那麼必須等待當前的請求執行完畢才能去關閉一些資源,否則就會出現各種異常。

除了等待,還需要讓外部的請求不要再過來,要告訴別人,我要下班了,不要來找我了,去找其他人吧。否則你永遠都下不了班,是一樣的道理。

非同步任務必須處理完

這裡的非同步任務通常指我們放入執行緒池中進行處理的任務,如果強制進行程式的停止,那麼執行緒池裡的任務就會丟掉,所以除了同步被外部呼叫的邏輯要處理完,這種非同步的邏輯也是要處理完的。

這裡再提一點,就是如果非同步任務丟失會對業務造成影響的這種場景,建議還是不要放到執行緒池裡面進行處理,如果要放,那麼必須有持久化,程式重啟後可以繼續執行。

訊息必須消費完

訊息也是非同步任務的一種型別,我們的目標肯定也是需要讓訊息消費完才行。但是訊息跟執行緒池裡的任務最大的差別就在於:訊息是有持久化的,並且有重試功能。

就算訊息沒消費完,程式強制停止,這條訊息沒有ACK,然後就會重試到另一臺機器的例項上繼續執行,前提是你的這個執行邏輯不能產生髒資料,一定要通過事務保證資料的一致性。

優雅下線解決方案

登出服務例項

下線最重要的一件事情就是登出自己的例項,這樣才不會有後續的請求過來。登出例項主要是跟註冊中心互動,將自己的例項從註冊中心下線掉就行了。下線後服務消費者會重新從註冊中心拉取最新的例項列表,也就不會將請求路由過來。

如果要下線的這個服務不是一個內部服務,而是閘道器呢?閘道器是流量的入口,客戶端的請求過來的,客戶端是自然不知道閘道器有多少例項,所以在閘道器前面都有一個負載均衡器,比如常用的Nginx。

那就需要將這個下線的閘道器例項從Nginx中進行下線操作,這樣後續的流量才不會被轉發過來,跟內部服務是一樣的道理。

Nginx如果有獨立的模組去對接註冊中心的話,那麼還是把註冊中心的給下線掉,Nginx就能感知到下線動作。如果沒有對接,而是固定的配置資訊,那麼就需要改Nginx的配置,然後重新載入即可。

登出MQ消費例項

通過下線註冊中心裡面的例項,外部流量就不會請求過來。此時還需要將MQ的例項進行下線操作,告訴MQ的服務端,不要再給我推訊息了或者是客戶端不再拉取訊息。

實現思路

  1. 寫一個停止流量的介面,在介面中將本身例項從註冊中心,MQ進行下線操作。
  2. 寫一個檢測流量是否結束的介面,在介面中判斷當前是否還有正在工作的執行緒,有沒有正在處理的訊息,有沒有正在執行的非同步任務等等。
  3. 當完全沒有流量的時候,釋出平臺直接對當前程式進行kill操作,此時所有任務都已執行完並且沒有新流量進來,無損操作。
  4. 執行釋出流程。
    這裡其實涉及到一個點,就是假如3分鐘了,還是有任務在處理,那麼是否要強制中斷?

這裡其實可以這麼做,就是我們的服務本身的例項一旦下線,正常的話幾秒鐘後就應該沒有任務了,因為對外的介面基本上都是毫秒級響應。主要就怕非同步任務,比如執行緒池裡堆積了好多工等待執行,所以大家需要去梳理下,如果有這種場景就調整,不要往執行緒池裡堆積任務,這樣才能保證在下流量的時候能夠儘快執行完成。

總結

其實優雅下線的核心在於流量的切換,就是我要下線的這個服務必須把所有外部的流量都切走,然後再把沒處理完的事情處理完,完成後就可以直接重新發布了。

如果你們上了容器的話,容器管理平臺應該是能夠提供優雅下線的方式,像K8s裡面應該就有優雅停止Pod的方式,不過我對K8s不太熟,記得是有的,其實原理也很簡單,先啟動一個Pod,完成之後將流量切過去就行了,這種方式更簡單,充分利用了容器的優勢。

相關文章