花6個月寫的付費專欄,免費送|仿開源框架從零到一完整實現高性能、可擴展的RPC框架

作者

渡碼,阿里巴巴碼農,公眾號:渡碼 作者,專註大數據開發、數據分析和Python技術。
avatar

關注公眾號 渡碼 回復關鍵字 manis,可獲取電子書、各章節和完整源程式碼,並且可加入讀者群一起交流問題。

簡介

19年上半年,我閱讀了Hadoop RPC模組的源程式碼,讀完後發現這個模組設計的非常好,與其他模組無耦合,完全可以獨立出來當成一個獨立的框架。為了總結學到的編程知識,同時也為了學習Apache頂級開源項目的程式碼是如何編寫的,我便把它做成了電子書,共350頁,從寫程式碼到做成電子書共花了6個月的時間。本來想做成付費專欄賺點小錢,並且已經到了上架階段了,但後來決定把它免費開放出來,讓更多的人能夠學習到優秀的實戰項目。

當然我們這本書並不是源碼分析類教程,而是強調動手能力。在這裡我會帶著大家按照 Hadoop RPC 源碼從 0 到 1 完整敲一遍,程式碼量在 4600 行左右。為了讓不熟悉 Hadoop 或 RPC 的朋友也能夠學習,我將 Hadoop RPC 稍微做了一點改造,賦予了新的業務含義,也有自己的名字,叫 Manis。Mnias 源碼相比於 Hadoop RPC源碼還原度為90%。為什麼不是100%呢?一方面為了突出重點,我會把不太重要、不是很核心的技術捨棄掉。另一方面為了符合新的業務定義,我會做一些改進,而不是照搬完全 Hadoop RPC。

雖然這個項目是實現 RPC 功能,但我覺得我們關注的重點不應該過多地放在 RPC 本身,而應該重點學習編寫 RPC 過程中所涉及的系統設計、面向對象設計思想和原則、工程/程式碼規範、客戶端開發、服務端開發、網路編程、多執行緒、並發編程、設計模式、異常處理等核心知識,可以說是麻雀雖小五臟俱全。尤其是對於剛學習 Java 還沒有接觸線上實戰項目的朋友,這是一次很好的練兵機會。

學習開源項目的一個優勢在於它是經過線上檢驗的,Hadoop集群規模最大達到上萬台服務端,足以證明它的 RPC 模組是優秀的。另外一個好處是可以積累頂級開源項目的開發經驗,大到架構設計,小到設計模式、程式碼規範,說不定日後就能為開源社區貢獻程式碼了。所以,學會了 Manis 後,不但有編寫實戰項目的經驗,同時也有能力閱讀 Hadoop RPC 的源碼,這也算是面試的加分項。

涉及到的核心技術

下面我們來介紹一下 Manis 中涉及的核心技術點。作為一個 RPC 框架,最關鍵的幾個模組是客戶端、網路模組和服務端

客戶端

作為客戶端來說,它的職責非常明確,以資料庫客戶端為例,它的職責就是向用戶提供增刪改查介面,並將相應的請求發送給服務端,接收結果並返回給用戶。由於客戶端職責邊界是非常明確的,所以我們從設計上就要將其與網路模組、與服務端解耦,解耦的方式就要用到設計模式中的代理模式。也就說客戶端只需要定義好它需要提供的功能(介面)並提供給用戶,具體如何實現就交給代理,至於代理是通過網路發送給服務端還是通過其他什麼方式客戶端就不需要關心了,客戶端只關心調用代理並拿結果。這樣做的好處是客戶端與其他模組解耦,提高了系統擴展性。當然,代理模式還有個容易被忽略的好處是它天然地適合用在 RPC 場景。

Manis 中支援多種序列化/反序列化方式,每種序列化方式對應一個類,它們都繼承共同的基類。我們在設計時需要做到不同序列化方式之間的程式碼是解耦的,且序列化/反序列化模組與客戶端模組、與網路模組是解耦的,這樣才能做到任意地增加新的新的序列化方式以及刪除老的序列化方式。為了實現客戶端與序列化/反序列化模組的松耦合,我們需要用到一些設計模式,比如,用適配器模式將客戶端定義的請求介面適配到不同序列化協議定義的請求介面。這樣做幾乎不需要修改現有的程式碼,符合面向對象的開閉原則

網路模組

下面再來說說網路模組。

由於客戶端的請求可能來自不同的序列化協議,但的目的是相同的,都是為了通過網路模組的服務端,可以說是殊途同歸。這樣的話,我們就有必要在網路這一層定義一個統一的協議(介面),讓不同序列化方式都遵循相同的協議(介面),那麼網路模組就可以對它們「一視同仁」,編寫一套程式碼就可以了。就好比,不管你用U盤還是硬碟,只要是 USB 介面,那都能插到電腦的同一個介面進行相同的讀寫邏輯。對於服務端的返回值也是採用同樣的處理邏輯。

網路模組必不可少的功能就是發送網路請求,當然除了這個還有一個更核心的功能是管理網路資源。聽起來有點抽象,如果用面向對象的思想來理解,其實就是創建一個類代表網路連接,比如就叫Connection類,每次創建一個網路連接其實就是創建一個Connection對象。當然,我們知道網路資源比較寶貴且創建成本較高,當系統客戶端請求量非常大的時候,我們不可能為每次請求都創建一個網路連接,所以,需要建立一個網路連接池,以達到復用網路資源的目的。我們可以再定義一個類ConnectionId,每個ConnectionId對象都唯一代表Connection對象,ConnectionId的屬性包含服務端地址請求網路的一些參數,所以我們可以認為客戶端請求服務端的地址和參數相同的話,就可以復用同一個網路連接。當然,這裡還有一個很關鍵的問題不容忽視,網路連接池是公共資源,為了保證執行緒安全,在對資源池讀寫時需要加鎖,也是從這裡開始本書加大了對並發編程的相關講解。剛剛介紹的這部分在 Manis 中是自主實現的。

建立網路連接的過程中還會涉及發送請求頭、請求上下文,裝飾網路輸入、輸出流等功能,這些比較偏業務,這裡就不再贅述了。

發送網路請求時,為了將業務程式碼與發送請求程式碼剝離,在 Manis 創建了一個建執行緒池,將發送發送請求的程式碼封裝成執行緒,丟到執行緒池中等待執行。所以,這裡又涉及到三部分知識

  • 使用工廠模式創建執行緒池,並用單例模式保證不被重複創建
  • 使用Java的ExecutorFuture,用來創建任務並等待返回
  • 對執行緒池的讀寫保證執行緒安全

最後,網路模組要實現的是等待服務端返回的結果。由於網路模組同一時間會接收大量客戶端網路請求,所以,我們可以創建一個單獨的執行緒,每隔一定時間輪詢是否有服務端的返回。

服務端

對於服務端來說,我們最關心的是性能問題。因為大量的客戶端請求最終都會匯總到服務端一個節點來處理。所以最原始的單執行緒+while循環的方式肯定滿足不了性能要求。所以比較最容易想到的改進點是多執行緒,雖然在一定程度上能解決第一種方式帶來的問題,但這種方式也有很大的缺點:頻繁創建執行緒成本比較大,並且執行緒之間的切換也需要一定的開銷,當執行緒數過多時顯然會降低服務端的性能。目前比較常用的解決方案是Reactor模式Reactor模式也分為單執行緒Reactor、多執行緒Reactor和多Reactor。這幾種的區別在書里都有具體說明,這裡我就不再介紹了。Reactor模式的優勢按照我自己的理解就四個字——各司其職。Manis 中使用的是多Reactor模式,設計圖如下:

簡單介紹一下圖中幾個執行緒的功能

  • Listener: 接收客戶端的連接請求,也可以叫做 Acceptor,封裝連接請求
  • Readr: 多執行緒並行地讀取客戶端請求,進行反序列化和解析操作
  • Handler: 多執行緒並行地讀取調用請求,解析調用方法並執行調用
  • Responder: 讀取響應結果,發送給客戶端

夠各司其職吧。那它們之間怎麼聯繫呢?從圖上可以看到是消息隊列,消息隊列可以很好地實現組件間的解耦。

雖然服務端的職責也比較明確、清晰,但涉及的內容一點不少,包括註冊不同的序列化方式,解析並調用相應的請求。最關鍵的是服務端執行緒是最多的,並且需要執行緒之間需要高度協調的,所以對並發編程的要求也更高,這塊書中也有重點講解。

最後我們看看Manis中核心組件的時序圖

avatar
avatar

由於 Manis 在設計上是足夠優秀的,所以開發的時候這三個模組可以並行進行。有點像近幾年web開發比較火的前後端分離架構,只要各個模組把協議定義好了後,開發就可以並行進行而不需要依賴彼此。至此,Manis 的核心技術就介紹完了,當然這只是冰山一角,畢竟 4600 行程式碼。

如何獲得本書

關注公眾號 渡碼 回復關鍵字 manis,可獲取電子書+源碼+讀者交流群。

目錄

程式碼結構

本書特色

在講解相冊內容同時,大部分章節都加入了課外拓展,針對每一節涉及的基礎知識,如:設計模式、序列化/反序列化基礎、單例測試、源碼分析、並發編程以及Hadoop源碼分析等內容都有拓展講解。力求讓零基礎的朋友也能跟上本書節奏,從0到1獨立完成一個項目。

希望你學完本書後不只學會了某項技術,而是提高了設計實現整個系統的能力。

適宜人群

  1. Java工程師
  2. 想獨立做一個完整項目的朋友
  3. Hadoop 初學者
  4. 大數據開發人員

歡迎加入知識星球

Tags: