新聞中心
介紹

讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領域值得信任、有價值的長期合作伙伴,公司提供的服務項目有:域名申請、雅安服務器托管、營銷軟件、網(wǎng)站建設、于田網(wǎng)站維護、網(wǎng)站推廣。
當開發(fā)一個單體項目的時候,大家肯定都寫過類似的代碼。即服務提供方和服務調(diào)用方在一個服務中
- public interface HelloService {
- public String sayHello(String content);
- }
- public class HelloServiceImpl implements HelloService {
- @Override
- public String sayHello(String content) {
- return "hello, " + content;
- }
- }
- public class Test {
- public static void main(String[] args) {
- HelloService helloService = new HelloServiceImpl();
- String msg = helloService.sayHello("world");
- // hello world
- System.out.println(msg);
- }
- }
但是由于單體服務的諸多弊端,現(xiàn)在很多公司已經(jīng)將不相關的功能拆分到不同的服務中。
如何像調(diào)用本地服務一樣調(diào)用遠程服務呢?這時就不得不提RPC框架了(Remote Procedure Call,遠程過程調(diào)用)。他幫我們屏蔽了網(wǎng)絡通信,序列化等操作的實現(xiàn),真正做到了調(diào)用遠程服務和調(diào)用本地服務一樣方便。
知名的RPC框架有Spring Cloud,阿里巴巴的Dubbo,F(xiàn)acebook的Thrift,Google grpc等
RPC的調(diào)用過程
一個RPC調(diào)用的過程如下
- 調(diào)用方發(fā)送請求后由代理類將調(diào)用的方法,參數(shù)組裝成能進行網(wǎng)絡傳輸?shù)南Ⅲw
- 調(diào)用方將消息體發(fā)送到提供方
- 提供方將消息進行解碼,得到調(diào)用的參數(shù)
- 提供方反射執(zhí)行相應的方法,并將結果返回
下面我們就分析一下rpc框架是怎么實現(xiàn)的?有哪些地方可以擴展。為了讓大家有一個更形象的認識,我寫了一個github項目,由簡到難實現(xiàn)了一個rpc框架,歡迎star
https://github.com/erlieStar/simple-rpc
生成代理類
前面我們說過,調(diào)用方執(zhí)行方法后,實際上執(zhí)行的是代理類的方法,代理類幫我們進行序列化和編解碼操作。那么如何生成代理類呢?
我們看一下主流的做法。
Facebook的Thrift和Google的grpc都是定義一個schema文件,然后執(zhí)行程序,幫你生成客戶端代理類,以及接口。調(diào)用方直接用生成的代理類來請求,提供方繼承生成的接口即可。
這種方式最大的優(yōu)點就是能進行多語言通信,即一份schema文件可以生成Java程序,也可以生成Python程序。調(diào)用方是Java程序,提供方是Python程序都能正常進行通訊。而且是二進制協(xié)議,通訊效率比較高。
在Java中生成代理類的方式有如下幾種
- JDK動態(tài)代理(實現(xiàn)InvocationHandler接口)
- 字節(jié)碼操作類庫(如cglib,Javassist)
在Dubbo中提供了2種生成代理類的方式,jdk動態(tài)代理和Javassist,默認是javassist,至于原因嗎?當然是javassist的效率更高
協(xié)議
為什么需要協(xié)議這個東西呢?Spring Cloud是通過Http協(xié)議來進行通訊的,那么Dubbo是通過哪種協(xié)議來進行通訊的?
為什么需要協(xié)議這個東西?
因為數(shù)據(jù)是以二進制的形式在網(wǎng)絡中傳輸中,RPC的請求數(shù)據(jù)并不是以一個整體發(fā)送到提供方的,而是可能被拆分成多個數(shù)據(jù)包發(fā)送出去,那提供方怎么識別數(shù)據(jù)呢?
例如一個文本ABCDEF,提供方有可能依次收到的數(shù)據(jù)為ABC DEF,也有可能為AB CD EF。提供方該怎么處理這些數(shù)據(jù)呢?
簡單啊,定個規(guī)則就可以了。這個規(guī)則可以有很多種,這里舉3個例子
- 定長協(xié)議,協(xié)議內(nèi)容長度固定,如讀取到50個byte就開始decode操作,可以參考Netty的FixedLengthFrameDecoder
- 特殊結束符,定義一個消息結束的分隔符,如讀到\n,表示一個數(shù)據(jù)讀取完畢了,沒有讀到就一直讀,可以參考Netty的DelimiterBasedFrameDecoder
- 變長協(xié)議(協(xié)議頭+協(xié)議體),用一個定長來表示消息體的長度,剩下的內(nèi)容為消息體,如果你愿意的話,協(xié)議頭還會放一些常用的屬性,Http協(xié)議的Header就是協(xié)議頭,如content-type,content-length等??梢詤⒖糔etty的DelimiterBasedFrameDecoder
Dubbo通過自定義協(xié)議來進行通訊,協(xié)議頭格式如下
每個位代表的含義如下
Dubbo為什么要自定義協(xié)議,而不用現(xiàn)成的Http協(xié)議?
最主要的原因就是自定義協(xié)議可以提高性能
Http協(xié)議的請求包比較大,有很多無用的內(nèi)容。自定義協(xié)議可以精簡很多內(nèi)容
Http協(xié)議是無狀態(tài)的,每次都要重新建立連接,響應完畢后將連接關閉
序列化
協(xié)議頭的內(nèi)容是通過位來表示的,協(xié)議體在應用程序中則會被封裝成對象,如Dubbo將請求封裝成Request,將響應封裝成Response
前面我們說過網(wǎng)絡傳輸?shù)臄?shù)據(jù)必須是二進制數(shù)據(jù),但調(diào)用方的入?yún)⒑吞峁┓降姆祷刂刀际菍ο螅虼诵枰蛄谢头葱蛄谢倪^程
序列化的方式有如下幾種
- JDK原生序列化
- JSON
- Protobuf
- Kryo
- Hessian2
- MessagePack
我們選擇序列化的方式時,主要考慮如下幾個因素
- 效率
- 空間開銷
- 通用性和兼容性
- 安全性
通訊
常見的IO模型有如下四種
- 同步阻塞IO(Blocking IO)
- 同步非阻塞IO(Non-blocking IO)
- IO多路復用(IO Multiplexing)
- 異步IO(Asynchronous IO)
因為RPC一般用在高并發(fā)的場景下,因此我們選擇IO多路復用這種模型,Netty的IO多路復用基于Reactor開發(fā)模式來實現(xiàn),后續(xù)的文章我會分析一下這種開發(fā)模式是如何支持高并發(fā)的
注冊中心
注冊中心的作用和電話簿類似。保存了服務名稱和具體的服務地址之間的映射關系,當我們想和某個服務進行通信時,只需要根據(jù)服務名就能查到服務的地址。
更重要的是這個電話簿是動態(tài)的,當某個服務的地址改變時,電話簿上的地址就會改變,當某個服務不可用時,電話簿上的地址就會消失
這個動態(tài)的電話簿就是注冊中心。
注冊中心的實現(xiàn)方式有很多種,Zookeeper,Redis,Nocas等都可以實現(xiàn)
介紹一下用Zookeeper實現(xiàn)注冊中心的方式
zookeeper有兩種類型的節(jié)點,持久節(jié)點和臨時節(jié)點
當我們往zookeeper上注冊服務的時候,用的是臨時節(jié)點,這樣當服務斷開時,節(jié)點能被刪除
| 節(jié)點類型 | 解釋 |
|---|---|
| 持久節(jié)點 | 將節(jié)點創(chuàng)建為持久節(jié)點,數(shù)據(jù)會一直存儲在zookeeper服務器上,即使創(chuàng)建該節(jié)點的客戶端與服務端的會話關閉了,該節(jié)點依然不會被刪除 |
| 持久順序節(jié)點 | 在持久節(jié)點的基礎上增加了節(jié)點有序的特性 |
| 臨時節(jié)點 | 將節(jié)點創(chuàng)建為臨時節(jié)點,數(shù)據(jù)不會一直存儲在zookeeper服務器上,當創(chuàng)建該臨時節(jié)點的客戶端會話關閉時,該節(jié)點在相應的zookeeper服務器上被刪除 |
| 臨時順序節(jié)點 | 在臨時節(jié)點的基礎上增加了節(jié)點有序的特性 |
注冊中心全部掛掉該怎么通信?
當一臺zookeeper掛掉后,會自動切換到另一個zookeeper。全部掛掉也沒有關系,因為dubbo把映射關系保存了一份在本地,這個映射關系可以保存在Map中,也可以保存在文件中
新的服務注冊到注冊中心,本地緩存會更新嗎?
注冊了監(jiān)聽的話,當然會更新啊。當被監(jiān)聽的節(jié)點或者子節(jié)點發(fā)生變化的時候,會將相應的內(nèi)容推送給監(jiān)聽的客戶端,你就可以更新本地的緩存了
Zookeeper中的事件如下
你可以把這個監(jiān)聽理解為分布式的觀察者模式
小結
當然一個成熟的RPC框架還得考慮很多內(nèi)容,例如路由策略,異常重試,監(jiān)控,異步調(diào)用等,和主流程相關度不大,就不多做介紹了
本文轉(zhuǎn)載自微信公眾號「Java識堂」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系Java識堂公眾號。
網(wǎng)站標題:如何手寫了一個RPC框架
網(wǎng)站網(wǎng)址:http://fisionsoft.com.cn/article/dhisshh.html


咨詢
建站咨詢
