# 03 如何自己實現一個RPC框架?
## 1. 如果讓你自己設計 RPC 框架你會如何設計?
> 注意:我們這里說的 RPC 框架指的是:可以讓客戶端直接調用服務端方法就像調用本地方法一樣簡單的框架,比如我前面介紹的 Dubbo、Motan、gRPC 這些。 如果需要和 HTTP 協議打交道,解析和封裝 HTTP 請求和響應。這類框架并不能算是“RPC 框架”,比如 Feign。
**一般情況下, RPC 框架不僅要提供服務發現功能,還要提供負載均衡、容錯等功能,這樣的 RPC 框架才算真正合格的。**
為了便于小伙伴們理解,我們先從一個最簡單的 RPC 框架使用示意圖開始。這也是 [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 目前的架構 。

從上圖我們可以看出:**服務提供端 Server 向注冊中心注冊服務,服務消費者 Client 通過注冊中心拿到服務相關信息,然后再通過網絡請求服務提供端 Server。**
作為 RPC 框架領域的佼佼者[Dubbo](https://github.com/apache/dubbo)的架構如下圖所示,和我們上面畫的大體也是差不多的。

下面我們再來看一個比較完整的 RPC 框架使用示意圖如下:

**參考上面這張圖,我們簡單說一下設計一個最基本的 RPC 框架的思路或者說實現一個最基本的 RPC 框架需要哪些東西:**
### 1.1. 1.注冊中心
注冊中心首先是要有的。比較推薦使用 Zookeeper 作為注冊中心。
ZooKeeper 為我們提供了高可用、高性能、穩定的分布式數據一致性解決方案,通常被用于實現諸如數據發布/訂閱、負載均衡、命名服務、分布式協調/通知、集群管理、Master 選舉、分布式鎖和分布式隊列等功能。并且,ZooKeeper 將數據保存在內存中,性能是非常棒的。 在“讀”多于“寫”的應用程序中尤其地高性能,因為“寫”會導致所有的服務器間同步狀態。(“讀”多于“寫”是協調服務的典型場景)。
關于 ZooKeeper 的更多介紹可以看我總結的這篇文章:[《ZooKeeper相關概念總結》](https://snailclimb.gitee.io/javaguide/#/docs/system-design/distributed-system/zookeeper/zookeeper-intro)
當然了,如果你想通過文件來存儲服務地址的話也是沒問題的,不過性能會比較差。
**注冊中心負責服務地址的注冊與查找,相當于目錄服務。** 服務端啟動的時候將服務名稱及其對應的地址(ip+port)注冊到注冊中心,服務消費端根據服務名稱找到對應的服務地址。有了服務地址之后,服務消費端就可以通過網絡請求服務端了。
我們再來結合 Dubbo 的架構圖來理解一下!

上述節點簡單說明:
- **Provider:** 暴露服務的服務提供方
- **Consumer:** 調用遠程服務的服務消費方
- **Registry:** 服務注冊與發現的注冊中心
- **Monitor:** 統計服務的調用次數和調用時間的監控中心
- **Container:** 服務運行容器
調用關系說明:
1. 服務容器負責啟動,加載,運行服務提供者。
1. 服務提供者在啟動時,向注冊中心注冊自己提供的服務。
1. 服務消費者在啟動時,向注冊中心訂閱自己所需的服務。
1. 注冊中心返回服務提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數據給消費者。
1. 服務消費者,從提供者地址列表中,基于軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
1. 服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。
### 1.2. 2.網絡傳輸
**既然我們要調用遠程的方法,就要發送網絡請求來傳遞目標類和方法的信息以及方法的參數等數據到服務提供端。**
網絡傳輸具體實現你可以使用 **Socket** ( Java 中最原始、最基礎的網絡通信方式。但是,Socket 是阻塞 IO、性能低并且功能單一)。
你也可以使用同步非阻塞的 I/O 模型 **NIO** ,但是用它來進行網絡編程真的太麻煩了。不過沒關系,你可以使用基于 NIO 的網絡編程框架 Netty ,它將是你最好的選擇!
我先簡單介紹一下 Netty ,后面的文章中我會詳細介紹到。
1. **Netty 是一個基于 NIO 的 client-server(客戶端服務器)框架,使用它可以快速簡單地開發網絡應用程序。**
1. 它極大地簡化并簡化了 TCP 和 UDP 套接字服務器等網絡編程,并且性能以及安全性等很多方面甚至都要更好。
1. 支持多種協議如 FTP,SMTP,HTTP 以及各種二進制和基于文本的傳統協議。
### 1.3. 3.序列化和反序列化
要在網絡傳輸數據就要涉及到**序列化**。**為什么需要序列化和反序列化呢? **
因為網絡傳輸的數據必須是二進制的。因此,我們的 Java 對象沒辦法直接在網絡中傳輸。為了能夠讓 Java 對象在網絡中傳輸我們需要將其**序列化**為二進制的數據。我們最終需要的還是目標 Java 對象,因此我們還要將二進制的數據“解析”為目標 Java 對象,也就是對二進制數據再進行一次**反序列化**。
另外,不僅網絡傳輸的時候需要用到序列化和反序列化,將對象存儲到文件、數據庫等場景都需要用到序列化和反序列化。

JDK 自帶的序列化,只需實現 `java.io.Serializable`接口即可,不過這種方式不推薦,因為不支持跨語言調用并且性能比較差。
現在比較常用序列化的有 **hessian**、**kyro**、**protostuff** ......。我會在下一篇文章中簡單對比一下這些序列化方式。
### 1.4. 4.動態代理
動態代理也是需要的。很多人可能不清楚為啥需要動態代理?我來簡單解釋一下吧!
我們知道代理模式就是: 我們給某一個對象提供一個代理對象,并由代理對象來代替真實對象做一些事情。你可以把代理對象理解為一個幕后的工具人。 舉個例子:我們真實對象調用方法的時候,我們可以通過代理對象去做一些事情比如安全校驗、日志打印等等。但是,這個過程是完全對真實對象屏蔽的。
講完了代理模式,再來說動態代理在 RPC 框架中的作用。
前面第一節的時候,我們就已經提到 :**RPC 的主要目的就是讓我們調用遠程方法像調用本地方法一樣簡單,我們不需要關心遠程方法調用的細節比如網絡傳輸**。
**怎樣才能屏蔽程方法調用的底層細節呢?**
答案就是**動態代理**。簡單來說,當你調用遠程方法的時候,實際會通過代理對象來傳輸網絡請求,不然的話,怎么可能直接就調用到遠程方法。
相關文章: [代理模式詳解:靜態代理+JDK/CGLIB 動態代理實戰](https://snailclimb.gitee.io/javaguide/#/docs/java/basis/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3)
### 1.5. 5.負載均衡
負載均衡也是需要的。為啥?
舉個例子:我們的系統中的某個服務的訪問量特別大,我們將這個服務部署在了多臺服務器上,當客戶端發起請求的時候,多臺服務器都可以處理這個請求。那么,如何正確選擇處理該請求的服務器就很關鍵。假如,你就要一臺服務器來處理該服務的請求,那該服務部署在多臺服務器的意義就不復存在了。負載均衡就是為了避免單個服務器響應同一請求,容易造成服務器宕機、崩潰等問題,我們從負載均衡的這四個字就能明顯感受到它的意義。
### 1.6. 6.傳輸協議
我們還需要設計一個私有的 RPC 協議,這個協議是客戶端(服務消費方)和服務端(服務提供方)交流的基礎。
簡單來說:**通過設計協議,我們定義需要傳輸哪些類型的數據, 并且還會規定每一種類型的數據應該占多少字節。這樣我們在接收到二級制數據之后,就可以正確的解析出我們需要的數據。**這有一點像密文傳輸的感覺。
通常一些標準的 RPC 協議包含下面這些內容:
- **魔數** : 通常是 4 個字節。這個魔數主要是為了篩選來到服務端的數據包,有了這個魔數之后,服務端首先取出前面四個字節進行比對,能夠在第一時間識別出這個數據包并非是遵循自定義協議的,也就是無效數據包,為了安全考慮可以直接關閉連接以節省資源。
- **序列化器編號** :標識序列化的方式,比如是使用 Java 自帶的序列化,還是 json,kyro 等序列化方式。
- **消息體長度** : 運行時計算出來。
- ......
如果你想看 [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 的 RPC 協議設計的話,可以在 Netty 編解碼器相關的類中找到。
## 2. 實現一個最基本的 RPC 框架需要哪些技術?
剛剛我們已經聊了如何實現一個 RPC 框架,下面我們就來看看實現一個最基本的 RPC 框架需要哪些技術吧!
按照我實現的這一款基于 Netty+Kyro+Zookeeper 實現的 RPC 框架來說的話,你需要下面這些技術支撐:
### 2.1. Java
1. 動態代理機制;
1. 序列化機制以及各種序列化框架的對比,比如 hession2、kyro、protostuff;
1. 線程池的使用;
1. `CompletableFuture` 的使用;
1. ......
### 2.2. Netty
1. 使用 Netty 進行網絡傳輸;
1. `ByteBuf` 介紹;
1. Netty 粘包拆包;
1. Netty 長連接和心跳機制;
1. ......
### 2.3. Zookeeper
1. 基本概念;
1. 數據結構;
1. 如何使用 Netflix 公司開源的 zookeeper 客戶端框架 Curator 進行增刪改查;
1. ......