如何實現一個分佈式RPC框架

遠程過程調用(Remote Procedure CallRPC)是一個計算機通訊協議。該協議容許運行於一臺計算機的程序調用另外一臺計算機的子程序,而程序員無需額外地爲這個交互做用編程。RPC的主要目標是讓構建分佈式應用更加容易,在提供強大的遠程調用能力的同時不損失本地調用的語義的簡潔性。html

趁實習前的這段業餘時間,我實現了一個輕量級的分佈式RPC框架,名字叫作 buddha,代碼量不大,可是麻雀雖小卻五臟俱全。本篇文章將一步步闡明buddha的設計、框架組件的拆解以及須要考慮的因素。git

序列化與反序列化

在網絡中,全部的數據都將會被轉化爲字節進行傳送,因此在代碼層面上,一個RPC框架須要實現特定格式的數據與字節數組之間的相互轉化。像Java已經提供了默認的序列化方式,可是若是是在高併發的場景下,使用Java原生的序列化方式可能會遇到性能瓶頸。因而,出現了許多開源的、高效的序列化框架:如KryofastjsonProtobuf等。buddha目前支持Kryofastjson兩種序列化框架。程序員

TCP拆包、粘包

因爲TCP只關心字節流,並不知曉上層的數據格式。若是客戶端應用層一次要發送的數據過大時,TCP會將該數據進行分解傳送,所以在服務端須要進行粘包處理(由TCP來保證數據的有序性);若是客戶端一次要發送的數據量很小時,TCP並不會立刻把數據發送出去,而是將其存儲在緩衝區,當達到某個閾值的時候再發送出去,所以在服務端須要進行拆包的工做。github

經過以上分析,咱們瞭解了TCP粘包或者拆包的緣由,解決這個問題的關鍵在於向數據包添加邊界信息,經常使用的方法有以下三個。編程

  • 發送端給每一個數據包添加包首部,首部中至少包含數據包的長度,這樣在接收端接收到數據時,經過讀取首部的長度信息獲得該數據包有效數據的長度。json

  • 發送端將每一個數據包封裝爲固定長度(多餘用0填充),這樣接收端在接收到數據後根據約定好的固定長度讀取每一個數據包的數據。數組

  • 使用特殊符號將每一個數據包區分開來,接收端也是經過該特殊符號的劃分數據包的邊界。緩存

buddha採用第一種方式來解決TCP拆包、粘包的問題。網絡

BIO與NIO

BIO每每用於經典的每鏈接每線程模型,之因此使用多線程,是由於像accept()read()write()等函數都是同步阻塞的,這意味着當應用爲單線程且進行IO操做時,若是線程阻塞那麼該應用必然會進入掛死狀態,可是實際上此時CPU是處於空閒狀態的。開啓多線程,就可讓CPU去爲更多的線程服務,提升CPU的利用率。可是在活躍線程數較多的狀況下,採用多線程模型迴帶來以下幾個問題。多線程

  • 線程的建立和銷燬代價頗高,在Linux操做系統中,線程本質上就是一個進程,建立和銷燬線程屬於重量級的操做。

  • JVM中,每一個線程會佔用固定大小的棧空間,而JVM的內存空間是有限的,所以若是線程數量過多那麼線程自己就會佔據過多的資源。

  • 線程的切換成本較高,每次線程切換須要涉及上下文的保存、恢復以及用戶態和內核態的切換。若是線程數過多,那麼會有較大比例的CPU時間花費在線程切換上。

使用線程池的方式解決前兩個問題,可是線程切換帶來的開銷仍是存在。因此在高併發的場景下,傳統的BIO是無能爲力的。而NIO的重要特色是:讀、寫、註冊和接收函數,在等待就緒階段都是非阻塞的,能夠當即返回,這就容許咱們不使用多線程充分利用CPU。若是一個鏈接不能讀寫,能夠把這個事件記錄下來,而後切換到別的就緒的鏈接進行數據讀寫。在buddha中,Netty被用來編寫結構更加清晰的NIO程序。

服務註冊與發現

在實際應用中,RPC服務的提供者每每須要使用集羣來保證服務的穩定性與可靠性。所以須要實現一個服務註冊中心,服務提供者將當前可用的服務地址信息註冊至註冊中心,而客戶端在進行遠程調用時,先經過服務註冊中心獲取當前可用的服務列表,而後獲取具體的服務提供者的地址信息(該階段能夠進行負載均衡),根據地址信息向服務提供者發起調用。客戶端能夠緩存可用服務列表,當註冊中心的服務列表發生變動時須要通知客戶端。同時,當服務提供者變爲不可用狀態時也須要通知註冊中心服務不可用。buddha使用ZooKeeper實現服務註冊與發現功能。

代碼實現

buddha是我學習驗證RPC過程當中誕生的一個輕量級分佈式RPC框架,代碼放在了 GitHub

參考

相關文章
相關標籤/搜索