guide-rpc-framework 是一個 一款基于 Netty+Kyro+Zookeeper 實作的自定義 RPC 架構。目前已經開源,位址:https://github.com/Snailclimb/guide-rpc-framework (覺得不錯再 Star!),歡迎小夥伴們指導建議!
如果 guide-rpc-framework 有任何需要改進和完善的地方,歡迎送出 PR/ISSUE ,和我一起完善!ღ( ´・ᴗ・` )比心
前言
雖說 RPC 的原理實際不難,但是,自己在實作的過程中自己也遇到了很多問題。guide-rpc-framework 目前隻實作了 RPC 架構最基本的功能,一些可優化點都在下面提到了,有興趣的小夥伴可以自行完善。
通過這個簡易的輪子,你可以學到 RPC 的底層原理和原理以及各種 Java 編碼實踐的運用。
你甚至可以把 guide-rpc-framework 當做你的畢設/項目經驗的選擇,這是非常不錯!對比其他求職者的項目經驗都是各種系統,造輪子肯定是更加能赢得面試官的青睐。
如果你要将 guide-rpc-framework 當做你的畢設/項目經驗的話,我希望你一定要搞懂,而不是直接複制粘貼我的思想。你可以 fork 我的項目,然後進行優化。如果你覺得的優化是有價值的話,你可以送出 PR 給我,我會盡快處理。
介紹
guide-rpc-framework 是一款基于 Netty+Kyro+Zookeeper 實作的 RPC 架構。代碼注釋詳細,結構清晰,并且內建了 Check Style 規範代碼結構,非常适合閱讀和學習。
由于 Guide哥自身精力和能力有限,如果大家覺得有需要改進和完善的地方的話,歡迎 fork 本項目,然後 clone 到本地,在本地修改後送出 PR 給我,我會在第一時間 Review 你的代碼。
我們先從一個基本的 RPC 架構設計思路說起!
一個基本的 RPC 架構設計思路
注意 :我們這裡說的 RPC 架構指的是:可以讓用戶端直接調用服務端方法就像調用本地方法一樣簡單的架構,比如我前面介紹的 Dubbo、Motan、gRPC 這些。如果需要和 HTTP 協定打交道,解析和封裝 HTTP 請求和響應。這類架構并不能算是“RPC 架構”,比如 Feign。
一個最簡單的 RPC 架構使用示意圖如下圖所示,這也是 guide-rpc-framework 目前的架構 :
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yM0UzMzczMjNmMjhTMwEjMlBTY5gTO1IGNmVWZmFmMh9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
img
服務提供端 Server 向注冊中心注冊服務,服務消費者 Client 通過注冊中心拿到服務相關資訊,然後再通過網絡請求服務提供端 Server。
作為 RPC 架構領域的佼佼者Dubbo的架構如下圖所示,和我們上面畫的大體也是差不多的。
img
一般情況下, RPC 架構不僅要提供服務發現功能,還要提供負載均衡、容錯等功能,這樣的 RPC 架構才算真正合格的。
簡單說一下設計一個最基本的 RPC 架構的思路:
img
- 注冊中心 :注冊中心首先是要有的,推薦使用 Zookeeper。注冊中心負責服務位址的注冊與查找,相當于目錄服務。服務端啟動的時候将服務名稱及其對應的位址(ip+port)注冊到注冊中心,服務消費端根據服務名稱找到對應的服務位址。有了服務位址之後,服務消費端就可以通過網絡請求服務端了。
- 網絡傳輸 :既然要調用遠端的方法就要發請求,請求中至少要包含你調用的類名、方法名以及相關參數吧!推薦基于 NIO 的 Netty 架構。
- 序列化 :既然涉及到網絡傳輸就一定涉及到序列化,你不可能直接使用 JDK 自帶的序列化吧!JDK 自帶的序列化效率低并且有安全漏洞。是以,你還要考慮使用哪種序列化協定,比較常用的有 hession2、kyro、protostuff。
- 動态代理 :另外,動态代理也是需要的。因為 RPC 的主要目的就是讓我們調用遠端方法像調用本地方法一樣簡單,使用動态代理可以屏蔽遠端方法調用的細節比如網絡傳輸。也就是說當你調用遠端方法的時候,實際會通過代理對象來傳輸網絡請求,不然的話,怎麼可能直接就調用到遠端方法呢?
- 負載均衡 :負載均衡也是需要的。為啥?舉個例子我們的系統中的某個服務的通路量特别大,我們将這個服務部署在了多台伺服器上,當用戶端發起請求的時候,多台伺服器都可以處理這個請求。那麼,如何正确選擇處理該請求的伺服器就很關鍵。假如,你就要一台伺服器來處理該服務的請求,那該服務部署在多台伺服器的意義就不複存在了。負載均衡就是為了避免單個伺服器響應同一請求,容易造成伺服器當機、崩潰等問題,我們從負載均衡的這四個字就能明顯感受到它的意義。
- ......
項目基本情況和可優化點
為了循序漸進,最初的是時候,我是基于傳統的 BIO 的方式 Socket 進行網絡傳輸,然後利用 JDK 自帶的序列化機制 來實作這個 RPC 架構的。後面,我對原始版本進行了優化,已完成的優化點和可以完成的優化點我都列在了下面 。
為什麼要把可優化點列出來? 主要是想給那些希望優化這個 RPC 架構的小夥伴一點思路。歡迎大家 fork 本倉庫,然後自己進行優化。
項目子產品概覽
img
運作項目
導入項目
fork 項目到自己的倉庫,然後克隆項目到自己的本地:git clone [email protected]:username/guide-rpc-framework.git,使用 IDEA 打開,等待項目初始化完成。
初始化 git hooks
這一步主要是為了在 commit 代碼之前,跑 Check Style,保證代碼格式沒問題,如果有問題的話就不能送出。
以下示範的是 Mac/Linux 對應的操作,Window 使用者需要手動将 config/git-hooks 目錄下的pre-commit 檔案拷貝到 項目下的 .git/hooks/ 目錄。
執行下面這些指令:
➜ guide-rpc-framework git:(master) ✗ chmod +x ./init.sh
➜ guide-rpc-framework git:(master) ✗ ./init.sh
init.sh 這個腳本的主要作用是将 git commit 鈎子拷貝到項目下的 .git/hooks/ 目錄,這樣你每次 commit 的時候就會執行了。
CheckStyle 插件下載下傳和配置
IntelliJ IDEA-> Preferences->Plugins->搜尋下載下傳 CheckStyle 插件,然後按照如下方式進行配置。
CheckStyle 插件下載下傳和配置
配置完成之後,按照如下方式使用這個插件!
插件使用方式
下載下傳運作 zookeeper
這裡使用 Docker 來下載下傳安裝。
下載下傳:
docker pull zookeeper:3.5.8
運作:
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8
使用
服務提供端
實作接口:
@Slf4j
@RpcService(group = "test1", version = "version1")
public class HelloServiceImpl implements HelloService {
static {
System.out.println("HelloServiceImpl被建立");
}
@Override
public String hello(Hello hello) {
log.info("HelloServiceImpl收到: {}.", hello.getMessage());
String result = "Hello description is " + hello.getDescription();
log.info("HelloServiceImpl傳回: {}.", result);
return result;
}
}
@Slf4j
public class HelloServiceImpl2 implements HelloService {
static {
System.out.println("HelloServiceImpl2被建立");
}
@Override
public String hello(Hello hello) {
log.info("HelloServiceImpl2收到: {}.", hello.getMessage());
String result = "Hello description is " + hello.getDescription();
log.info("HelloServiceImpl2傳回: {}.", result);
return result;
}
}
釋出服務(使用 Netty 進行傳輸):
/**
* Server: Automatic registration service via @RpcService annotation
*
* @author shuang.kou
* @createTime 2020年05月10日 07:25:00
*/
@RpcScan(basePackage = {"github.javaguide.serviceimpl"})
public class NettyServerMain {
public static void main(String[] args) {
// Register service via annotation
new AnnotationConfigApplicationContext(NettyServerMain.class);
NettyServer nettyServer = new NettyServer();
// Register service manually
HelloService helloService2 = new HelloServiceImpl2();
RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder()
.group("test2").version("version2").build();
nettyServer.registerService(helloService2, rpcServiceProperties);
nettyServer.start();
}
}
服務消費端
ClientTransport rpcClient = new NettyClientTransport();
RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder()
.group("test1").version("version1").build();
RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceProperties);
HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
String hello = helloService.hello(new Hello("111", "222"));
相關問題
為什麼要造這個輪子?Dubbo 不香麼?
寫這個 RPC 架構主要是為了通過造輪子的方式來學習,檢驗自己對于自己所掌握的知識的運用。
實作一個簡單的 RPC 架構實際是比較容易的,不過,相比于手寫 AOP 和 IoC 還是要難一點點,前提是你搞懂了 RPC 的基本原理。
我之前從理論層面在我的知識星球分享過如何實作一個 RPC。不過理論層面的東西隻是支撐,你看懂了理論可能隻能糊弄住面試官。咱程式員這一行還是最需要動手能力,即使你是架構師級别的人物。當你動手去實踐某個東西,将理論付諸實踐的時候,你就會發現有很多坑等着你。
大家在實際項目上還是要盡量少造輪子,有優秀的架構之後盡量就去用,Dubbo 在各個方面做的都比較好和完善。
如果我要自己寫的話,需要提前了解哪些知識
Java :
- 動态代理機制;
- 序列化機制以及各種序列化架構的對比,比如 hession2、kyro、protostuff。
- 線程池的使用;
- CompletableFuture 的使用
- ......
Netty :
- 使用 Netty 進行網絡傳輸;
- ByteBuf 介紹
- Netty 粘包拆包
- Netty 長連接配接和心跳機制
Zookeeper :
- 基本概念;
- 資料結構;
- 如何使用 Netflix 公司開源的 zookeeper 用戶端架構 Curator 進行增删改查;