一、基礎概念
RPC,即 Remote Procedure Call(遠程過程調用),說得通俗一點就是:調用遠程計算機上的服務,就像調用本地服務一樣。
RPC 可基於 HTTP 或 TCP 協議,Web Service 就是基於 HTTP 協議的 RPC,它具有良好的跨平臺性,但其性能卻不如基於 TCP 協議的 RPC。會兩方面會直接影響 RPC 的性能,一是傳輸方式,二是序列化。
但rpc在實際項目中基於http協議的webService使用的更多。
對於遠程調用的基於restful的webservice主要有以下幾種實現方式
1.org.springframework.web.client.RestTemplate
2.org.apache.commons.httpclient.HttpClient
可以使用這些組件來進行遠程調用,就像是在瀏覽器或者postman等工具上調用一樣的效果
二、RPC原理
由於各服務部署在不同機器,服務間的調用免不了網絡通信過程,服務消費方每調用一個服務都要寫一坨網絡通信相關的代碼,不僅複雜而且極易出錯。
如果有一種方式能讓我們像調用本地服務一樣調用遠程服務,而讓調用者對網絡通信這些細節透明,那麼將大大提高生產力,比如服務消費方在執行helloWorldService.sayHello(“test”)時,實質上調用的是遠端的服務。這種方式其實就是RPC(Remote Procedure Call Protocol),在各大互聯網公司中被廣泛使用,如阿里巴巴的hsf、dubbo(開源)、Facebook的thrift(開源)、Google grpc(開源)、Twitter的finagle(開源)等。
要讓網絡通信細節對使用者透明,我們需要對通信細節進行封裝,我們先看下一個RPC調用的流程涉及到哪些通信細節:
1.服務消費方(client)調用以本地調用方式調用服務;
2.client stub接收到調用後負責將方法、參數等組裝成能夠進行網絡傳輸的消息體;
3.client stub找到服務地址,並將消息發送到服務端;
4.server stub收到消息後進行解碼;
5.server stub根據解碼結果調用本地的服務;
6.本地服務執行並將結果返回給server stub;
7.server stub將返回結果打包成消息併發送至消費方;
8.client stub接收到消息,並進行解碼;
9.服務消費方得到最終結果。
RPC的目標就是要2~8這些步驟都封裝起來,讓用戶對這些細節透明。
架構設計
在一個典型RPC的使用場景中,包含了服務發現、負載、容錯、網絡傳輸、序列化等組件,其中RPC協議就指明瞭程序如何進行網絡傳輸和序列化 。
rpc協議基本組成
http 協議報文
請求消息Request
請求行,用來說明請求類型,要訪問的資源以及所使用的HTTP版本.
請求頭部,緊接着請求行(即第一行)之後的部分,用來說明服務器要使用的附加信息從第二行起爲請求頭部,HOST將指出請求的目的地.User-Agent,服務器端和客戶端腳本都能訪問它,它是瀏覽器類型檢測邏輯的重要基礎.該信息由你的瀏覽器來定義,並且在每個請求中自動發送等等
空行,請求頭部後面的空行是必須的
請求數據也叫主體,可以添加任意的其他數據。
響應消息Response
狀態行,由HTTP協議版本號, 狀態碼, 狀態消息 三部分組成。
消息報頭,用來說明客戶端要使用的一些附加信息
空行,消息報頭後面的空行是必須的
響應正文,服務器返回給客戶端的文本信息。
Dubbo 協議報文編碼
通過查看dubbo控制檯事列如下:
provider節點:
consumer節點:
其實質dubbo協議就是仿造http協議規範而進行自定義的。
Dubbo協議的編解碼過程
dubbo在編碼或解碼的過程中就會對參數或結果進行序列化。
技術選型
1.序列化
什麼是序列化?序列化就是將數據結構或對象轉換成二進制串的過程,也就是編碼的過程。
什麼是反序列化?將在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程。
爲什麼需要序列化?轉換爲二進制串後纔好進行網絡傳輸嘛!
爲什麼需要反序列化?將二進制轉換爲對象纔好進行後續處理!
Java 提供了默認的序列化方式,但在高併發的情況下,這種方式將會帶來一些性能上的瓶頸,於是市面上出現了一系列優秀的序列化框架,
比如:Protobuf、Kryo、Hessian、Jackson 等,它們可以取代 Java 默認的序列化,從而提供更高效的性能。
2.網路傳輸
爲了支持高併發,傳統的阻塞式 IO 顯然不太合適,因此我們需要異步的 IO,即 NIO。
Java 提供了 NIO 的解決方案,Java 7 也提供了更優秀的 NIO.2 支持,用 Java 實現 NIO 並不是遙不可及的事情,只是需要我們熟悉 NIO 的技術細節。
3.註冊中心
如何讓別人使用我們的服務呢?有同學說很簡單嘛,告訴使用者服務的IP以及端口就可以了啊。確實是這樣,這裏問題的關鍵在於是自動告知還是人肉告知。
人肉告知的方式:如果你發現你的服務一臺機器不夠,要再添加一臺,這個時候就要告訴調用者我現在有兩個ip了,你們要輪詢調用來實現負載均衡;調用者咬咬牙改了,結果某天一臺機器掛了,調用者發現服務有一半不可用,他又只能手動修改代碼來刪除掛掉那臺機器的ip。現實生產環境當然不會使用人肉方式。
有沒有一種方法能實現自動告知,即機器的增添、剔除對調用方透明,調用者不再需要寫死服務提供方地址?當然可以,現如今zookeeper被廣泛用於實現服務自動註冊與發現功能!
簡單來講,zookeeper可以充當一個服務註冊表(Service Registry),讓多個服務提供者形成一個集羣,讓服務消費者通過服務註冊表獲取具體的服務訪問地址(ip+端口)去訪問具體的服務提供者
心跳檢測設想:
可以使用一個定時任務線程每隔1s去檢測服務的長連接狀態
4.透明化遠程服務調用
怎麼封裝通信細節才能讓用戶像以本地調用方式調用遠程服務呢?對
java來說就是使用代理!
java代理有兩種方式:
1.jdk 動態代理
2.字節碼生成
儘管字節碼生成方式實現的代理更爲強大和高效,但代碼維護不易,大部分公司實現RPC框架時還是選擇動態代理方式。
消費端創建的代理對象主要的功能爲:請求編碼,請求參數序列化,網絡傳輸,返回解碼,返回結果反系列化等騙底層操作。
具體實現請參考:http://www.importnew.com/22003.html
根據以上技術需求,我們可使用如下技術選型:
Spring:它是最強大的依賴注入框架,也是業界的權威標準。
Netty:它使 NIO 編程更加容易,屏蔽了 Java 底層的 NIO 細節。
Protostuff:它基於 Protobuf 序列化框架,面向 POJO,無需編寫 .proto 文件。
ZooKeeper:提供服務註冊與發現功能,開發分佈式系統的必備選擇,同時它也具備天生的集羣能力。
jdk動態代理:
2.2 對消息進行編碼和解碼
2.2.1 確定消息數據結構
http 報文編碼原理
2.2.2 序列化和反序列化
具體實現請參考:http://www.importnew.com/22003.html
2.3 通信
2.4 requestID(待續)
3.發佈自己的服務
通過zookeeper來實現服務自動註冊與發現功能
具體實現請參考:http://www.importnew.com/22003.html
三、執行流程
1.分佈式RPC流程圖
Dubbo 和 Spring Cloud 有什麼區別?
兩個沒關聯,如果硬要說區別,有以下幾點。
1)通信方式不同
Dubbo 使用的是 RPC 通信,而 Spring Cloud 使用的是 HTTP RESTFul 方式。
2)組成部分不同
Dubbo | Spring Cloud | |
---|---|---|
服務註冊中心 | Zookeeper | Spring Cloud Netflix Eureka |
服務調用方式 | RPC | REST API |
服務服務網關 | 無 | Spring Cloud Netflix Zuul |
斷路器 | 不完善 | Spring Cloud Netflix Hystrix |
分佈式配置 | 無 | Spring Cloud Config |
服務跟蹤 | 無 | Spring Cloud Sleuth |
消息總線 | 無 | Spring Cloud Bus |
數據流 | 無 | Spring Cloud Stream |
批量任務 | 無 | Spring Cloud Task |
參考資料:
1)輕量級分佈式 RPC 框架:http://www.importnew.com/20327.html
2)RPC原理及RPC實例分析:http://www.importnew.com/22003.html