歡迎光臨
每天分享高質量文章

【剖析 | SOFARPC 框架】系列之 SOFARPC 優雅關閉剖析

SOFA

Scalable Open Financial Architecture

是螞蟻金服自主研發的金融級分佈式中間件,包含了構建金融級雲原生架構所需的各個組件,是在金融場景里錘煉出來的最佳實踐。


本文為《剖析 | SOFARPC 框架》第九篇,作者米麒麟,目前就職於陸金所

《剖析 | SOFARPC 框架》系列由 SOFA 團隊和原始碼愛好者們出品,

專案代號:,官方目錄目前已經全部認領完畢,文末提供了已完成的文章目錄。


  前言

眾所周知,在微服務架構下麵,當應用需要進行新功能升級發佈,或者異常關閉重啟的時候,我們會對應用的行程進行關閉,而在關閉之前,我們希望做一些諸如關閉資料庫連接,等待處理任務完成等操作,這個就涉及到我們本文中的優雅關閉功能。假如應用沒有支持優雅停機,則會帶來譬如資料丟失,交易中斷、檔案損壞以及服務未下線等情況。


微服務的優雅停機需要遵循”註銷發佈服務 → 通知註銷服務 → 更新服務清單 → 開啟請求屏蔽 → 呼叫銷毀業務服務 → 檢查所有請求是否完成 → 超時強制停機”應用服務停機流程。


SOFARPC 提供服務端/客戶端優雅關閉功能特性,用來解決 kill PID,應用意外自動退出譬如 System.exit() 退出 JVM,使用腳本或命令方式停止應用等使用場景,避免服務版本迭代上線人工干預的工作量,提高微服務架構的服務高可靠性。


本文將從行程的優雅關閉,SOFARPC 應用服務優雅關閉流程,Netty 的優雅停機等方面出發詳細剖析 。

  行程優雅關閉

Kill 結束行程

在 Linux上,kill 命令發送指定的信號到相應行程,不指定信號則預設發送 SIGTERM(15) 終止指定行程。如果無法終止,可以發送 SIGKILL(9) 來強制結束行程。kill 命令信號共有64個信號值,其中常用的是:


2 (SIGINT:中斷,Ctrl+C)。

15 (SIGTERM:終止,預設值)。

9 (SIGKILL:強制終止)。


這裡我們重點說一下 15 和 9 的情況。

kill PID/kill -15 PID 命令系統發送 SIGTERM 行程信號給響應的應用程式,當應用程式接收到 SIGTERM 信號,可以進行釋放相應資源後再停止,此時程式可能仍然繼續運行。


kill -9 PID 命令沒有給行程遺留善後處理的條件。應用程式將會被直接終止。


對微服務應用而言其效果等同於突然斷電,強行終止可能會導致如下幾方面問題:

  • 快取資料尚未持久化到磁盤,導致資料丟失;

  • 檔案寫操作正在進行未更新完成,突然退出行程導致檔案損壞;

  • 執行緒訊息佇列尚有接收到的請求訊息,未能及時處理,導致請求訊息丟失;

  • 資料庫事務提交,服務端提供給客戶端請求響應,訊息尚在通信執行緒發送佇列,行程強制退出導致客戶端無法接收到響應,此時發起超時重試帶來重覆更新。


所以支持優雅關閉的前提是關閉的時候,不能被直接 通過發送信號為 9 的 Kill 來強制結束。當然,其實我們也可以對外統一暴露應用程式管理的 API 來進行控制。本文暫時不做討論。

Java 優雅關閉

當應用程式收到信號為15的關閉命令時,可以進行相應的響應,Java 程式的優雅停機通常通過註冊 JDK 的 ShutdownHook 來實現,當應用系統接收到退出指令,首先 JVM 標記系統當前處於退出狀態,不再接收新的訊息,然後逐步處理推積的訊息,接著呼叫資源回收接口進行資源銷毀,例如記憶體清理、物件銷毀等,最後各執行緒退出業務邏輯執行。



優雅停機需要超時控制機制,即到達超時時間仍然尚未完成退出前資源回收等操作,則通過停機腳本呼叫kill-9 PID命令強制退出行程。


其中 JVM 優雅關閉的 流程主要的階段如下圖所示:



如圖所示,Java行程優雅退出流程包括如下五個步驟:

  1. 應用行程啟動,初始化 Signal 實體;

  2. 根據操作系統型別,獲取指定行程信號;

  3. 實現 SignalHandler 接口,實體化並註冊到 Signal,當 Java 行程接收到譬如 kill -12 或者 Ctrl+C 命令信號回呼其 handle() 方法;

  4. SignalHandler 的 handle 回呼接口初始化 ShutdownHook 執行緒,並將其註冊到 Runtime 的 ShutdownHook。

  5. Java 行程接收到終止行程信號,呼叫 Runtime 的exit() 方法退出 JVM 虛擬機,自動檢測用戶是否註冊ShutdownHook 任務,如果有則觸發 ShutdownHook 執行緒執行自定義資源釋放等操作。

  SOFARPC 優雅關閉

在行程可以進行優雅關閉後,SOFARPC 如何實現優雅關閉呢?首先 SOFARPC 對於所有可以被優雅關閉的資源設計com.alipay.sofa.rpc.base.Destroyable接口,通過向 JVM 的 ShutdownHook 註冊來對這些可被銷毀的資源進行優雅關閉,支持銷毀前和銷毀後操作。


這裡包括兩部分:

  1. 作為服務端註冊 JDK 的 ShutdownHook 執行取消服務註冊、關閉服務端口等動作實現;

  2. 作為客戶端通過實現 DestroyHook 接口逐步處理正在呼叫的請求關閉服務呼叫。

總體設計

運行時背景關係註冊 JDK 的 ShutdownHook 執行銷毀 SOFARPC 運行相關環境實現類似發佈平臺/用戶執行kill PID 優雅停機。運行時背景關係 RpcRuntimeContext 靜態初始化塊註冊 ShutdownHook 函式:

static {
    ...
    // 增加jvm關閉事件
    if (RpcConfigs.getOrDefaultValue(RpcOptions.JVM_SHUTDOWN_HOOK, true)) {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override 
            public void run() {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("SOFA RPC Framework catch JVM shutdown event, Run shutdown hook now.");
                }
                destroy(false);
            }
        }, "SOFA-RPC-ShutdownHook"));
    }
}

註冊本身很簡單,重要的是 destroy 方法實際上做的事情非常多。按照先後順序,大致包含如下幾個部分。



RpcRuntimeContext 銷毀服務優雅關閉完整流程:


  1. 設置 RPC 框架運行狀態修改為正在關閉,表示當前執行緒不再處理 RPC 服務請求;

  2. 遍歷運行時背景關係關閉資源的銷毀鉤子,進行註冊銷毀器清理資源前期準備工作;

  3. 獲取發佈的服務配置反註冊服務提供者,向指定註冊中心批量執行取消服務註冊;

  4. 檢查當前服務端連接和佇列任務,先把佇列任務處理完畢,再緩慢關閉啟動端口;

  5. 關閉發佈的服務,到註冊中心取消服務發佈,取消將處理器註冊到服務端,清除服務發佈配置快取狀態;

  6. 關閉呼叫的服務,斷開客戶端連接取消服務呼叫,清除服務訂閱配置快取,到註冊中心取消服務訂閱;

  7. 遍歷註冊中心配置逐一關閉註冊中心,移除指定註冊中心配置快取;

  8. 不可復用長連接管理器銷毀連接,關閉客戶端的公共連接資源,清理不可復用連接快取;

  9. 遍歷 RPC 框架背景關係已加載的模塊,逐步卸載模塊譬如負載均衡、鏈路追蹤等;

  10. 遍歷運行時背景關係關閉資源的卸載鉤子,進行註冊銷毀器清理資源後期收尾工作;

  11. 清理全部快取例如應用類加載器快取、服務類加載器快取以及方法物件快取等;

  12. 調整 RPC 框架運行狀態更新為關閉完畢,運行時背景關係釋放資源關閉服務行程。

作為服務端

總體設計包含非常多的優雅關閉步驟,這裡我們再單獨介紹一下作為服務端的時候,幾個核心步驟的原理和流程,作為服務端,SOFARPC 關閉服務行程不能直接暴力關閉,而是逐步進行關閉。需要進行如下幾個步驟:


  1. 反註冊服務:註冊中心工廠獲取全部註冊中心實體呼叫 batchUnRegister 方法批量取消服務註冊,通知服務消費者監聽器更新其服務提供者串列,避免服務消費者繼續取用下線服務造成服務呼叫異常不可用現象。

  2. 關閉端口:服務端工廠檢查執行緒池 bizThreadPool 佇列是否有正在執行的請求或者佇列里有請求,執行緒組呼叫 shutdownGracefully 方法緩慢關閉遠程服務端口,保證業務執行緒池佇列請求先處理完畢再關閉執行緒池以及端口。

  3. 銷毀服務物件:根據發佈/訂閱服務配置關閉提供/呼叫的服務,呼叫 unExport/unRefer 方法進行取消服務發佈/訂閱,註冊中心刪除發佈/訂閱服務配置,清理髮布/訂閱服務配置快取,防止產生 RPC 服務發佈/訂閱服務物件。


RpcRuntimeContext 銷毀服務配置資源核心實現入口:

com.alipay.sofa.rpc.context.RpcRuntimeContext#destroy()

作為客戶端

作為客戶端,SOFARPC 通過實現 DestroyHook 銷毀鉤子接口提供優雅關閉的鉤子,把 GracefulDestroyHook 關閉鉤子註冊到長連接管理器銷毀客戶端連接方法。客戶端優雅關閉連接實際上是 Cluster 的關閉,關閉呼叫的服務實現入口:

com.alipay.sofa.rpc.client.AbstractCluster#destroy()

GracefulDestroyHook 鉤子優雅關閉連接整體流程:

  • 銷毀前準備斷連:獲取當前 Client 正在發送的呼叫數量和服務消費方斷連超時時間配置,檢查是否有正在呼叫的服務請求並且當前運行時背景關係時間未到達指定超時時間,滿足準備條件則當前執行緒睡眠10秒;

  • 銷毀時釋放資源:關閉重連執行緒 reconThread,關閉客戶端長連接,清空當前存活+重試的客戶端串列,多執行緒執行銷毀已經建立的客戶端長連接,逐步處理正在呼叫的服務請求並且下線服務呼叫請求操作。



其中 GracefulDestroyHook 優雅關閉鉤子銷毀前準備斷連操作:



是一個自旋檢查的操作。

  Netty 優雅關閉

SOFARPC 在關閉自身 RpcServer 的時候,也會關閉啟動的 Netty 服務端。這時候就涉及到 Netty 的優雅關閉。

Netty 作為高性能的異步 NIO 通信框架,負責各種通信協議的接入,解析和調度,SOFABolt 是基於 Netty 最佳實踐的輕量、易用、高性能、易擴展的通信框架。當微服務應用行程優雅停機,作為基礎通信框架的 Netty 需要考慮優雅停機控制,主要原因包括以下幾方面因素:

  1. 儘快釋放 NIO 執行緒,清理物件句柄資源;

  2. 使用 flush 批量發送訊息,需要發送積攢在通信佇列等待發送的訊息;

  3. 正在進行 read 和 write 的訊息需要繼續處理;

  4. NioEventLoop 執行緒調度器配置的定時任務需要執行或者清理。


這裡是 Netty底層的實現邏輯,我們只要知道在關閉 Server的時候,需要進行相應的方法呼叫即可。



可以看到

  1. 設置 NioEventLoop 執行緒狀態修改為 ST_SHUTTING_DOWN,表示當前執行緒不再處理請求訊息;

  2. 確認關閉操作:將通信佇列等待發送或者正在發送的訊息發送完畢,把已經到期或者關閉超時之前到期的定時任務執行結束,把用戶自定義註冊到 NioEventLoop 執行緒的 ShutdownHook 關閉鉤子執行完成;

  3. 清理資源操作:把註冊到多路復用器 Selector 的 Channel 釋放,持有多路復用器 Selector 去註冊和關閉,通信佇列和定時任務清空取消,修改 NioEventLoop 執行緒狀態為 ST_TERMINATED 關閉執行緒。


其中,Netty 的優雅停機核心實現入口:

io.netty.channel.EventLoopGroup#shutdownGracefully()

  SOFABoot 優雅關閉

一個完整的微服務可能不僅僅包括SOFARPC,還可能會用到各種各樣的中間件,也涉及到各種流量調度等行為,所以優雅關閉是需要和發佈平臺聯動的。如果強制 kill, 那麼目前的這些優雅關閉的方案都不會生效。


所以在後續的 SOFABoot 版本中我們會增加接收一套完整的運維API,方便發佈管控平臺進行呼叫。SOFABoot 接收通過接收「關閉運維指令」而不是單純依賴 ShutdownHook 邏輯,然後觸發各個中間件的優雅關閉行為,其中就包括SOFAPRC的主動反註冊服務發佈和服務呼叫等關閉動作,各個中間件的優雅關閉執行完成後,SOFABoot 行程再退出。

  總結

本文從行程的優雅關閉,到 SOFARPC 的優雅關閉支持,並詳細介紹 Netty 優雅關閉的原理。在設計優雅關閉的時候,可以考慮按照如下幾個約定來進行實現。

(1) 應用能夠支持優雅停機

(2) 優先註銷註冊中心註冊的服務實體

(3) 待停機的服務應用的接入點標記拒絕服務

(4) 上游服務支持故障轉移因優雅停機而拒絕的服務

(5) 根據實際業務場景提供適當的停機接口。

相關鏈接

SOFA 文件: http://www.sofastack.tech/

SOFA: https://github.com/alipay

SOFARPC: https://github.com/alipay/sofa-rpc

SOFABolt: https://github.com/alipay/sofa-bolt

  《剖析 | SOFARPC 框架》系列歷史文章

參與有獎調研,幫助 SOFA 成長

歡迎大家共同打造 SOFAStack https://github.com/alipay


赞(0)

分享創造快樂