微信公衆號:內核小王子
關注可瞭解更多關於數據庫,JVM內核相關的知識;
若是你有任何疑問也能夠加我pigpdong[^1]java
前面咱們瞭解了,服務調用方和服務提供方,如何可以經過註冊中心作到水平擴展,從而知足高可用和高併發,那麼服務之間如何才能實現相互調用呢?web
綜合上一節的內容,服務雙方無非就兩種模式,一種直接經過網絡調用,另外一種經過中間代理進行轉發,那麼不管哪種咱們只須要在服務雙方經過socket,弄一個channel,一邊write,一邊read就能夠搞定了spring
可是仔細一想,咱們要解決的問題不只僅是網絡傳輸的問題,例如,如何定義遠程調用的接口,參數,返回結果,若是解決跨語言的問題,異步,重試,如何能提供跟本地調用同樣便利的接口等
不過這些問題前人已經幫咱們都想好了,bruce jay寫了一遍論文,來定義RPC的標準,之後你們開發的RPC框架都是按照這個標準來,例如JAVA原生的RMI和dubbo.數據庫
爲了統一,咱們將服務調用方稱爲客戶端,將服務提供方稱爲服務端,當客戶端發起一個遠程調用的時候,他須要一個STUB,做爲他的本地代理,由這個stub來將接口描述和參數以約定好的格式,經過網絡包發給服務端,在服務端返回後,在由這個stub將返回結果給客戶端,
而這個stub須要RPC框架在調用以前就生成,生成stub他須要先經過服務端供的接口描述文件,這個描述文件若是在java環境能夠是一個jar包,或者是一個約定好結構的IDL(接口描述語言)文件,RPC框架根據這個描述文件生成STUB代理程序,這個stub能夠是一個進程,也能夠是一個java類,不一樣的框架實現不一樣.
而服務端也會事先生成一個stub,由這個stub程序在收到網絡請求後,根據約定的格式,將其解析出接口地址和參數,而後調用對應的服務提供方返回結果,下面咱們來分析一下每一步的實現.json
接口描述文件IDL主要是爲了,描述爲了讓客戶端怎麼知道服務端提供了哪些接口,這些接口有哪些方法,方法有哪些參數,方法返回什麼數據,這時候咱們想到WSDL文件,沒錯,基於XML的SOAP協議就是早期的
rpc程序webservice的接口描述文件,以及後來基於json的RESTFUL接口協議,這兩種都是支持跨語言的,facebook開源的thrift和google的grpc也是使用這種IDL接口定義語言,以後經過編譯器生成不一樣語言的stub,
而若是服務雙方都是java語言環境的,那麼只須要將服務端的接口定義的class打成一個jar包提供給客戶端既可.微信
客戶端須要將調用的接口名經過網絡包傳給客戶端,同時也要將方法參數傳輸過去,而方法參數多是一個java對象(對象裏的屬性可能仍是一個引用,指向其餘對象),這時須要將這些java對象轉換成二進制包從網絡傳輸,而後服務端接收到網絡包後在另一個JVM下將其還原成相應的java對象,這個過程就是
對象的序列化和反序列化,客戶端的Stub在收到服務端返回的結果後也須要反序列化成java對象,服務端在發送返回結果的時候也須要進行序列化操做,因此對客戶端和服務端都會有對應的序列化和反序列化,因此必定要約定好客戶端和服務端使用的序列化標準是一致的
,咱們生產環境就發生過,因爲雙方序列化框架版本不一致致使的事故,目前開源的序列化框架有不少,阿里的fastjson在使用和性能上都還不錯,還有gson,protobuffer,thrift等,這裏強調一下因爲各家序列化框架實現不一樣,有些是經過類的屬性進行反射,有些根據屬性對應的get方法,因此爲了下降
序列化框架帶來的影響,咱們須要針對JAVA bean須要有一套標準的命名規範,例如布爾類success get方法isSuccess set方法setSuccess,千萬不要將屬性命名爲isSuccess這種.網絡
在java環境下,Stub能夠直接做爲一個工具類,讓客戶端像調用本地方法同樣調用遠程方法,咱們能夠根據服務端提供的的接口描述,經過java動態代理或者字節碼工具生成這個接口的一個實現類,在這個實現類裏經過Stub去
調用遠程,將參數序列化和接口地址經過網絡發給服務端,並將返回結果的二進制數據反序列化爲返回的對象,那麼Stub做爲一個工具類,處理網絡鏈接,失敗重試,序列化和反序列化等,而動態生成的代理類都依賴該工具,客戶端實際用到的是這個代理類,
另外咱們能夠思考下,怎麼實現客戶端的異步調用以及回調,異步調用是指,客戶端代理類將調用請求經過stub發給服務端後不等待服務端的結果,能夠直接繼續後面的流程,或者也能夠等到服務端返回後執行一個回調.
這裏咱們能夠參考dubbo的作法,將經過網絡發送請求的過程封裝在一個Future對象裏,若是是同步調用就直接調用future.get等待結果,若是是異步調用就直接將future對象放入一個threadlocal對象RpcContext裏就返回,若是須要執行異步回調,就監聽這個future對象返回後執行註冊的通知程序併發
目前須要進行網絡傳輸的中間件通常都會選擇netty,咱們有必要簡單瞭解下netty的線程模型,像dubbo,grpc都會分爲應用線程和netty線程框架
netty使用簡單,預置了多種編解碼實現,支持多種主流協議,成熟,穩定,社區活躍度高,定製能力強,經過ChannelHandler能夠靈活擴展異步
netty線程模型
在服務端Stub也能夠做爲一個工具類,處理網絡傳輸,序列化等,同時還須要根據解析到的接口地址,找到對應的實現類而後在調用,因爲這裏須要動態調用,須要用到java的反射機制,不過也有像grpc這種
將服務實現註冊到stub,而後能夠在Stub中實現直接調用的
RPC框架的目標就是讓遠程服務調用更加簡單,透明,RPC框架負責屏蔽底層的網絡傳輸方式(TCP或者UDP),序列化方式(XML,JSON,二進制)和通信細節,服務調用者能夠像調用本地接口同樣,調用遠程的服務提供者,而不須要關心底層的通信細節和調用過程.
.下圖反映了幾大主流RPC框架的通用實現.
1.支持多語言的RPC框架,google的gRPC,Apache(facebook)的Thrift 2.只支持特定語言的RPC框架,例如新浪的Motan 3.支持服務治理等服務化特性的分佈式框架,例如阿里的dubbo 4.擁有完整生態的spring cloud