螞蟻集團網絡通訊框架 SOFABolt 功能介紹及協議框架解析 | 開源

<SOFA:Channel/>,有趣實用的分佈式架構頻道。
回顧視頻以及 PPT 查看地址見文末。歡迎加入直播互動釘釘羣 : 30315793,不錯過每場直播。

SOFAChannel#17

你們好,我是本期 SOFAChannel 的分享講師丞一,來自螞蟻集團,是 SOFABolt 的開源負責人。今天咱們來聊一下螞蟻集團開源的網絡通訊框架 SOFABolt 的框架解析以及功能介紹。本期分享將從如下四個方面展開:git

  • SOFABolt 簡介;
  • 基礎通訊能力解析;
  • 協議框架解析;
  • 私有協議實現解析;

SOFABolt 是什麼

SOFABolt 產生背景

相信你們都知道 SOFAStack,SOFAStack(Scalable Open Financial Architecture Stack)是一套用於快速構建金融級雲原生架構的中間件,也是在金融場景裏錘鍊出來的最佳實踐。程序員

SOFABolt 則是 SOFAStack 中的網絡通訊框架,是一個基於 Netty 最佳實踐的輕量、易用、高性能、易擴展的通訊框架,他的名字 Bolt 取自迪士尼動畫《閃電狗》。他一開始是怎麼在螞蟻集團內部產生的,咱們能夠類比一下 Netty 的產生緣由:github

  • 爲了讓 Java 程序員能將更多的精力放在基於網絡通訊的業務邏輯實現上,而不是過多的糾結於網絡底層 NIO 的實現以及處理難以調試的網絡問題,Netty 應運而生;
  • 爲了讓中間件開發者能將更多的精力放在產品功能特性實現上,而不是重複地一遍遍製造通訊框架的輪子,SOFABolt 應運而生;

這些年,在微服務與消息中間件在網絡通訊上,螞蟻集團解決過不少問題、積累了不少經驗並持續進行着優化和完善,咱們把總結的解決方案沉澱到 SOFABolt 這個基礎組件裏並反饋到開源社區,但願可以讓更多使用網絡通訊的場景受益。目前該組件已經運用在了螞蟻集團中間件的微服務 (SOFARPC)、消息中心、分佈式事務、分佈式開關、以及配置中心等衆多產品上。編程

同時,已有數家企業在生產環境中使用了 SOFABolt,感謝你們的確定,也但願 SOFABolt 能夠給更多的企業帶來實踐價值。安全

SOFABolt 企業用戶

以上企業信息根據企業用戶 Github 上反饋統計 — 截止 2020.06。網絡

SOFABolt:https://github.com/sofastack/sofa-bolt架構

SOFABolt 框架組成

SOFABolt 總體能夠分爲三個部分:併發

  • 基礎通訊能力(基於 Netty 高效的網絡 IO 與線程模型、鏈接管理、超時控制);
  • 協議框架(命令與命令處理器、編解碼處理器);
  • 私有協議實現(私有 RPC 通訊協議的實現);

下面,咱們分別介紹一下 SOFABolt 每一個部分的具體能力。框架

基礎通訊能力

基礎通訊模型

基礎通訊模型

如上圖所示,SOFABolt 有多種通訊模型,分別爲:oneway、sync、future、callback。下面,咱們介紹一下每一個通訊模型以及他們的使用場景。異步

  • oneway:不關注結果,即客戶端發起調用後不關注服務端返回的結果,適用於發起調用的一方不須要拿到請求的處理結果,或者說請求或處理結果能夠丟失的場景;
  • sync:同步調用,調用線程會被阻塞,直到拿到響應結果或者超時,是最經常使用的方式,適用於發起調用方須要同步等待響應的場景;
  • future:異步調用,調用線程不會被阻塞,經過 future 獲取調用結果時纔會被阻塞,適用於須要併發調用的場景,好比調用多個服務端並等待全部結果返回後執行特定邏輯的場景;
  • callback:異步調用,調用線程不會被阻塞,調用結果在 callback 線程中被處理,適用於高併發要求的場景;

oneway 調用的場景很是明確,當調用方不須要拿到調用結果的時候就可使用這種模式,可是當須要處理調用結果的時候,選擇使用同步的 sync 仍是使用異步的 future 和 callback?都是異步調用,又如何在 future、callback 兩種模式中選擇?

顯然同步能作的事情異步也能作,可是異步調用會涉及到線程上下文的切換、異步線程池的設置等等,較爲複雜。若是你的場景比較簡單,好比整個流程就一個調用並處理結果,那麼建議使用同步的方式處理;若是整個過程須要分幾個步驟執行,能夠拆分不一樣的步驟異步執行,給耗時的操做分配更多的資源來提高系統總體的吞吐。

在 future 和 callback 的選擇中,callback 是更完全的異步調用,future 適用於須要協調多個異步調用的場景。好比須要調用多個服務,而且根據多個服務端響應結果執行邏輯時,能夠採用 future 的模式給多個服務發送請求,在統一對全部的 future 進行處理完成協同操做。

超時控制機制

在上一部分的通訊模型中,除了 oneway 以後,其餘三種(sync、future、callback)都須要進行超時控制,由於用戶須要在預期的時間內拿到結果。超時控制簡單來講就是在用戶發起調用後,在預期的時間內若是沒有拿到服務端響應的結果,那麼此次調用就超時了,須要讓用戶感知到超時,避免一直阻塞調用線程或者 callback 永遠得不到執行。

在通訊框架中,超時控制必需要知足高效、準確的要求,由於通訊框架是分佈式系統的基礎組件,一旦通訊框架出現性能問題,那麼上層系統的性能顯然是沒法提高的。超時控制的準確性也很是重要,好比用戶預期一次調用最多隻能執行3秒,由於超時控制不許確致使用戶調用時線程被阻塞了4秒,這顯然是不能接受的。

超時控制

SOFABolt 的超時控制採用了 Netty 中的 HashedWheelTimer,其原理如上圖。假設一次 tick 表示100毫秒,那麼上面的時間輪 tick 一輪表示800毫秒,若是須要在300毫秒後觸發超時,那麼這個超時任務會被放到'2'的 bucket 中,等到 tick 到'2'時則被觸發。若是一個超時任務須要在900毫秒後觸發,那麼它會被放到如'0'的 bucket 中,並標記 task 的 remainingRounds=1,當第一次 tick 到'0'時發現 remainingRounds 不等於0,會對 remainingRounds 進行減1操做,當第二次 tick 到'0',發現這個任務的 remainingRounds 是0,則觸發這個任務。

若是將時間輪的一次 tick 設置爲1秒,ticksPerWheel 設置爲60,那麼就是現實時鐘的秒針,走完一圈表明一分鐘。若是一個任務須要再1分15秒後執行,就是標記爲秒針走一輪以後指向第15格時觸發。關於時間輪的原理推薦閱讀下面這篇論文:
《Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility》。

快速失敗機制

超時控制機制能夠保證客戶端的調用在一個預期時間以後必定會拿到一個響應,不管這個響應是由服務端返回的真實響應,仍是觸發了超時。若是由於某些緣由致使客戶端的調用超時了,而服務端在超時以後實際將響應結果返回給客戶端了會怎麼樣?

這個響應結果在客戶端會被丟棄,由於對應的請求已經由於超時被釋放掉,服務端的這個響應會由於找不到對應的請求而被丟棄。既然響應在請求超時以後返回給客戶端會被丟棄,那麼在肯定請求已經超時的狀況下服務端是否能夠不處理這個請求而直接返回超時的響應給客戶端?——這就是 SOFABolt 的快速失敗機制。

快速失敗機制

快速失敗機制能夠減輕服務端的負擔,使服務端儘快恢復服務。好比由於某些外部依賴的因素致使服務端處理一批請求產生了阻塞,而此時客戶端還在將更多的請求發送到服務端堆積在 Buffer 中等待處理。當外部依賴恢復時,服務端由於要處理已經在 Buffer 中的請求(實際這些請求已經超時,處理這些請求將沒有業務意義),而致使後續正常的請求排隊阻塞。加入快速失敗機制後,在這種狀況下能夠將 Buffer 中的請求進行丟棄而開始服務當前新增的未超時的請求,使的服務能快速的恢復。

快速失敗機制-2

快速失敗機制的前提條件是能判斷出一個請求已經超時,而判斷超時須要依賴時間,依賴時間則須要統一的時間參照。在分佈式系統中是沒法依賴不一樣的機器上的時間的,由於網絡會有延遲、機器時間的時間會有誤差。爲了不參照時間的不一致(機器之間的時鐘不一致),SOFABolt 的快速失敗機制只依賴於服務端機器自身的時鐘(統一的時間參照),判斷請求已經超時的條件爲:

System.currentTimestamp - request.arriveTimestamp > request.timeout

request.arriveTimestamp 爲請求達到服務端時的時間,request.timeout 爲請求設置的超時時間,由於請求從客戶端發出到服務端須要時間,因此當以到達時間來計算時,若是這個請求已經超時,那麼這個請求在客戶端側必然已經超時,能夠安全的將這個請求丟棄。

具體分佈式系統中時間和順序等相關的文件推薦閱讀《Time, Clocks, and the Ordering of Events in a Distributed System》,Lamport 在此文中透徹的分析了分佈式系統中的順序問題。

協議框架

協議框架

SOFABolt 中包含的協議命令如上圖所示。在 RPC 版本的協議命令中只包含兩類:RPC 請求/響應、心跳的請求/響應。RPC 的請求/響應負責攜帶用戶的請求數據和響應數據,心跳請求用於鏈接的保活,只攜帶少許的信息(通常只包含請求 ID 之類的必要信息便可)。

有了命令以後,還須要有命令的編解碼器和命令處理器,以實現命令的編解碼和處理。RemotingCommand 的處理模型以下:

emotingCommand 的處理模型

整個請求和響應的過程設計的核心組件如上圖所示,其中:

  • 客戶端側:

    • Connection 鏈接對象的封裝,封裝對底層網絡的操做;
    • CommandEncoder 負責編碼 RemotingCommand,將 RemotingCommand 按照私有協議編碼成 byte 數據;
    • RpcResponseProcessor 負責處理服務端的響應;
  • 服務端側:

    • CommandDecoder 分別負責解碼 byte 數據,按照私有協議將 byte 數據解析成 RemotingCommand 對象;
    • RpcHandler 按照協議碼將 RemotingCommand 轉發到對應的 CommandHandler 處理;
    • CommandHandler 按照 CommandCode 將 RemotingCommand 轉發到對應的 RpcRequestProcessor 處理;
    • RpcRequestProcessor 按照 RemotingCommand 攜帶對象的 Class 將請求轉發到用戶的 UserProcessor 執行業務邏輯,並將結果經過 CommandDecoder 編碼後返回給客戶端;

私有協議實現

內置私有協議實現

SOFABolt 除了提供基礎通訊能力外,內置了私有協議的實現,能夠作到開箱即用。內置的私有協議實現是通過實踐打磨的,具有擴展性的私有協議實現。

內置私有協議

  • proto:預留的協議碼字段,當協議發生較大變動時,能夠經過協議碼進行區分;
  • ver1:肯定協議以後,經過協議版原本兼容將來協議的小調整,好比追加字段;
  • type:標識 Command 類型:oneway、request、response;
  • cmdcode:命令碼,好比以前介紹的 RpcRequestCommand、HeartbeatCommand 就須要用不一樣的命令碼進行區分;
  • ver2:Command 的版本,用於標識同一個命令的不一樣版本;
  • requestId:請求的 ID,用於惟一標識一個請求,在異步操做中經過此 ID 來映射請求和響應;
  • codec:序列化碼,用於標識使用哪一種方式來進行業務數據的序列化;
  • switch:協議開關,用於標識是否開啓某些協議層面的能力,好比開啓 CRC 校驗;
  • timeout:客戶端進行請求時設置的超時時間,快速失敗機制所依賴的超時時間;
  • classLen:業務請求類的的類名長度;
  • headerLen:業務請求頭的長度;
  • contentLen:業務請求體的長度;
  • className:業務請求類的類名;
  • header:業務請求頭;
  • content:業務請求體;
  • CRC32:CRC校驗碼;

實現自定義協議

在 SOFABolt 中實現私有協議的關鍵是實現編解碼器(CommandEncoder/CommandDecoder)及命令處理器(CommandHandler)。

編解碼器

上面是爲了在 SOFABolt 中實現自定義私有協議鎖須要編寫的類。SOFABolt 將編解碼器及命令處理器都綁定到 Protocol 對象上,每一個 Protocol 實現都有一組本身的編解碼器和命令處理器。

Protocol 對象

在編解碼器中實現自定義的私有協議。在設計私有協議時必定要考慮好協議的可拓展性,以便在將來進行功能加強時不會出現協議沒法兼容的狀況。

可拓展性

完成編解碼以後剩餘工做就是實現處理器。處理器分爲兩塊:命令處理入口 CommandHandler 及具體的業務邏輯執行器 RemotingProcessor。

命令處理入口 CommandHandler

具體的業務邏輯執行器 RemotingProcessor

完成以上工做後,使用 SOFABolt 實現自定義私有協議通訊的開發工做基本完成了,可是在實際編寫這部分代碼時會遇到種種困難及限制,主要體如今如下一些方面:

  • 擴展性不足:好比在 RpcClient 中默認使用了內置的編解碼器,且沒有預留接口進行設置,當使用自定義協議時只能繼承 RpcClient 進行覆蓋;
  • 框架和協議耦合:好比默認提供了 CommandHandler->RemotingProcessor->UserProcessor 這樣的處理模型,可是這個模型和協議耦合嚴重(依賴於 CommandCode 和 RequestCode),致使使用自定義協議時只能本身實現 CommandHandler,而後本身在實現請求的分發邏輯等,至關於要重寫 CommandHandler->RemotingProcessor->UserProcessor 這個模型;
  • 協議限制:雖然能夠經過自定義 Encoder 和 Decoder 實現自定義協議,可是框架內部組織時都依賴 ProtocolCode,致使須要將 ProtocolCode 加入到協議中,限制了用戶設計私有協議的自由;

整體而言,當前 SOFABolt 提供了很是強大的通訊能力和多年沉澱的協議設計。若是用戶須要去適配本身當前已經在運行的私有協議還有能夠完善的地方,根本緣由仍是在於設計之初是貼合這 RPC 框架來設計的(從不少代碼的命名上也能看出來),因此在協議和框架的分離上能夠作的更好。

總結

本次分享從 SOFABolt 總體框架的實現開始,介紹了 SOFABolt 的基礎通訊模型、超時控制以及快速失敗機制,着重分析了私有協議實現的示例,總結而言 SOFABolt 提供了:

  • 基於 Netty 的最佳實踐;
  • 基礎的通訊模型和高效的超時控制機制、快速失敗機制;
  • 內置的私有協議實現,開箱即用;

歡迎 Star SOFABolt:https://github.com/sofastack/sofa-bolt

以上就是本期分享的主要內容。由於直播時間有限,關於 SOFABolt 更詳細的介紹,能夠閱讀「剖析 SOFABolt 框架」系列文章,由 SOFABolt 團隊以及開源社區同窗共同出品:

「剖析 SOFABolt 框架」解析:https://www.sofastack.tech/blog/ 點擊 tag 「剖析 | SOFABolt 框架」

one more thing

SOFABolt 目前也存在能夠提高完善的地方,在嘗試實現徹底自定義的私有協議時是相對困難的,須要對代碼作一些繼承改造。

針對這個現狀,咱們在「阿里巴巴編程之夏」活動中提交了一個 SOFABolt 的課題:「拆分 SOFABolt 的框架和協議」,但願先經過拆分框架和協議,以後再進行模塊化的處理,使 SOFABolt 成爲一個靈活的、可拓展的通訊框架最佳實踐!

歡迎你們一塊兒共建來解決這個問題,讓 SOFABolt 變得更好:
https://github.com/sofastack/sofa-bolt/issues/224

SOFAStack 也歡迎更多開源愛好者加入社區共建,成爲社區 Contributor、Committer

SOFACommunity:https://www.sofastack.tech/community/

本期視頻回顧以及 PPT 查看地址

https://tech.antfin.com/commu...

相關文章
相關標籤/搜索