什么是RPC?RPC框架dubbo的核心流程
一、REST 與 RPC:
1、什么是 REST 和 RPC 協議:
????????在單體應用中,各模塊間的調用是通過編程語言級別的方法函數來實現,但分布式系統運行在多臺機器上,一般來說,每個服務實例都是一個進程,服務間必須使用進程間通信機制來交互,而常見的通信協議主要有 RPC 和 REST 協議。
(1)REST:
????????REST 是基于 HTTP 實現,使用 HTTP 協議處理數據通信,更加標準化與通用,因為無論哪種語言都支持 HTTP 協議。常見的 http API 都可以稱為 Rest 接口。REST 是一種架構風格,指一組架構約束條件和原則,滿足 REST 原則的應用程序或設計就是 RESTful,RESTful 把一切內容都視為資源。REST 強調組件交互的擴展性、接口的通用性、組件的獨立部署、以及減少交互延遲的中間件,它強化安全,也能封裝遺留系統。
(2)RPC:
????????RPC 是一種進程間通信方式,允許像調用本地服務一樣調用遠程服務,通信協議大多采用二進制方式。
2、RPC 與 REST 的對比:
| 報文格式 | 二進制 | XML、JSON |
| 網絡協議 | TCP/HTTP/HTTP2 | HTTP/HTTP2 |
| 序列化開銷 | 低 | 一般 |
| 網絡開銷 | 低 | 一般 |
| 性能 | 高 | 一般 |
| 訪問便利性 | 客戶端比較方便,但二進制消息不可讀 | 文本消息開發者可讀,瀏覽器可訪問 |
| 代碼耦合度 | 耦合度高 | 松散耦合 |
| 通用性 | 低,對外開發需進一步轉換成REST協議 | 高,可直接對外開發 |
| 使用場景 | 內部服務 | 外部服務 |
(1)傳輸協議與性能:RPC 的傳輸協議靈活,可基于 TCP 實現,由于 TCP 協議處于協議棧的下層,能夠更靈活地對協議字段進行定制,讓請求報文體積更小,減少網絡開銷,提高傳輸性能并縮短傳輸耗時,實現更大的吞吐量和并發數。REST 的 HTTP 協議是上層協議,發送包含同等內容的信息,請求中會包含很多無用的內容,所占用的字節數比使用 TCP 協議傳輸更高,因此在同等網絡下,HTTP 會比基于 TCP 協議的數據傳輸效率要低,傳輸耗時更長,不僅如此,REST 的 HTTP 大部分是通過 JSON 來實現的,序列化也更消耗性能,但如果是基于 HTTP2.0,那么經過封裝也是可以作為一個 RPC 來使用的。
(2)靈活性、開放性與通用性:REST 通過 HTTP 實現,相對更加規范與通用,無論哪種語言都支持 HTTP 協議,所以 REST 的調用和測試都很方便,但使用 RPC 則會有很多約束,而如果 RPC 需要對外開放的話,需要進一步處理,靈活性不如 REST
(3)使用場景:REST 主要用于對外開放的異構環境,比如瀏覽器接口調用,Api 接口調用,第三方接口調用等。RPC 主要用于公司內部的服務調用,性能消耗低,傳輸效率高,特別是大型的網站,內部子系統較多、接口非常多的情況下適合使用 RPC
?
二、RPC 框架:
????????REST 和 RPC 都常用于微服務架構中,微服務的好處之一,就是不限定服務的提供方使用什么技術選型,能夠實現大公司跨團隊的技術解耦。 但是,如果沒有統一的通信框架,各個團隊的服務提供方就需要各自實現一套序列化、反序列化、網絡框架、連接池、收發線程、超時處理、狀態機等 “業務之外” 的重復技術勞動,造成整體的低效。所以,統一通信框架把上述 “業務之外” 的技術勞動統一處理,是服務化首要解決的問題。
1、什么是 RPC 框架:
????????RPC 框架的目標就是讓遠程服務調用更簡單、透明,由 RPC 框架負責屏蔽底層的序列化、傳輸方式和通信的細節,開發者在使用時只需要了解誰在什么位置提供了什么樣的遠程服務接口即可,并不需要關心底層通信細節和調用過程。RPC 框架作為架構微服務化的基礎組件,它能大大降低架構微服務化的成本,提高調用方與服務提供方的研發效率。
2、RPC 框架的技術架構:
????????如下圖,在典型 RPC 的使用場景中,主要包含了服務發現、負載、容錯、網絡傳輸、序列化等組件,其中 ”RPC協議”就指明了服務如何進行序列化和網絡傳輸,這也是RPC的核心功能。
- 應用級的RPC框架:Dubbo、Google gRPC
- 通信框架:Netty
- 遠程通信協議:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)
?3、RPC 框架的調用流程:
3.1、RPC 框架的核心組件:
(1)客戶端(Client):服務調用方。
(2)客戶端存根(Client Stub):存放服務端地址信息,將客戶端的請求參數數據信息打包成網絡消息,再通過網絡傳輸發送給服務端。
(3)服務端存根(Server Stub):接收客戶端發送過來的請求消息并進行解包,然后再調用本地服務進行處理。
(4)服務端(Server):服務的真正提供者。
3.2、RPC 的調用流程:
(1)服務消費者(Client 客戶端)通過本地調用的方式調用需要消費的服務
(2)客戶端存根(Client Stub)接收到調用請求后負責將方法、入參等信息序列化(組裝)成能夠進行網絡傳輸的消息體
(3)客戶端存根(Client Stub)找到遠程的服務地址,并且將消息通過網絡發送給服務端
(4)服務端存根(Server Stub)收到消息后進行解碼,反序列化操作
(5)服務端存根(Server Stub)根據解碼結果調用本地的服務進行相關處理
(6)服務端(Server)執行具體的業務邏輯,并將處理結果返回給服務端存根(Server Stub)
(7)服務端存根(Server Stub)將返回結果序列化,并通過網絡發送給消費方
(8)客戶端存根(Client Stub)接收到消息,并進行解碼與反序列化
(9)服務消費方得到最終結果;
而RPC框架的實現目標則是將上面的第2-10步完好地封裝起來,也就是把調用、編碼/解碼的過程給封裝起來,讓用戶感覺上像調用本地服務一樣的調用遠程服務
4、如何實現一個RPC框架:
????????通過上面幾點的介紹,如果要我們實現一個 RPC 框架,我們應該如果做呢?要實現 一個RPC 框架,我們只需要解決的以下幾件最基本的事情:
4.1、如何進行網絡通訊:
????????遠程調用中,客戶端和服務端的通訊是基于網絡連接的,所以首先需要建立通信連接,通過這個連接把請求信息的字節流傳給服務端,然后再把序列化后的響應結果傳回客戶端,在這個通訊過程中,它所使用的協議是沒有限制的,能完成傳輸就行,但是在這里我們需要考慮兩個問題:如何選擇網絡協議 和 如何建立連接。
(1)網絡協議的選擇:多數 RPC 框架選擇 TCP 作為傳輸協議,但其實 UDP 也可以,也有部分選擇HTTP,比如 gRPC 使用 HTTP2,但是不同的協議各有優劣勢,TCP 更加高效,而 HTTP 在實際應用中更加的靈活,具體需要根據使用場景來選擇,下文會介紹如何選擇正確的網絡傳輸協議
(2)通訊連接的建立:RPC 所有交換的數據都在這個連接里傳輸,這個連接可以是按需連接(需要調用時就先建立連接,調用結束后就立馬斷掉),也可以是長連接(客戶端和服務器建立起連接之后保持長期持有,不管此時有無數據包的發送,可以配合心跳檢測機制定期檢測建立的連接是否存活有效),多個遠程過程調用共享同一個連接。
4.2、如何那行服務尋址:
????????解決尋址的問題,也就是說服務端如何確定客戶端要調用的函數,在本地調用中,函數是直接通過函數指針來指定的,但是在遠程調用中,函數指針是不行的,因為兩個進程的地址空間是完全不一樣的。所以在遠程調用中,客戶端和服務端需要分別維護一個【ID -> 函數】的映射表,ID在所有進程中都是唯一確定的,客戶端在做遠程過程調用時,附上這個ID,服務端通過查表,來確定客戶端需要調用的函數,然后執行相應函數的代碼。
????????而尋址問題的具體實現方式,則可以通過注冊中心,服務提供者完成后,對外暴露相應的功能并將自己注冊到注冊中心上,接著服務消費者從注冊中心尋找服務,然后調用該服務對應的方法完成遠程調用
(1)從服務提供者的角度看:
- 當服務提供者啟動的時候,需要將自己提供的服務注冊到指定的注冊中心,以便服務消費者能夠通過服務注冊中心進行查找;
- 當服務提供者由于各種原因致使提供的服務停止時,需要向注冊中心注銷停止的服務;服務的提供者需要定期向服務注冊中心發送心跳檢測,服務注冊中心如果一段時間未收到來自服務提供者的心跳后,認為該服務提供者已經停止服務,則將該服務從注冊中心上去掉。
(2)從調用者的角度看:
- 服務的調用者啟動的時候根據自己訂閱的服務向服務注冊中心查找服務提供者的地址等信息;
- 當服務調用者消費的服務上線或者下線的時候,注冊中心會告知該服務的調用者;
- 服務調用者下線的時候,則取消訂閱。
4.3、如何序列化和反序列化:
????????在本地調用中,我們只需要把參數信息壓到內存棧中,然后讓函數自己去棧中讀取,但是遠程過程調用時,客戶端跟服務端是不同的進程,不能通過內存來傳遞參數。所以遠程過程調用中,客戶端和服務端交互時,方法的參數和結果需要通過底層的網絡協議如TCP傳遞,由于網絡協議是基于二進制的(只有二進制數據才能在網絡中傳輸),那么這些值需要序列化成二進制的形式,通過尋址和傳輸將序列化的二進制發送目標服務器。目標服務器接收到數據時,需要對數據進行反序列化。序列化和反序列化的速度也會影響遠程調用的效率。
- 將對象轉換成二進制流的過程叫做序列化
- 將二進制流轉換成對象的過程叫做反序列化
5、如何選擇正確的PRC網絡傳輸協議?
????????在 RPC 中可選的網絡傳輸方式有多種,比如 TCP 協議、UDP 協議、HTTP 協議。每一種協議對整體的性能和效率都有不同的影響,那如何選擇一個正確的網絡傳輸協議呢?針對這個問題,我們首先要搞明白各種傳輸協議在 RPC 中的工作方式:
- 基于 TCP 的協議實現的 RPC 調用,由于 TCP 協議處于協議棧的下層,能夠更加靈活地對協議字段進行定制,讓請求報文體積更小,減少網絡開銷,提高傳輸性能并縮短傳輸耗時,實現更大的吞吐量和并發數。但是需要更多關注底層復雜的細節,實現的代價更高,同時對不同平臺,如安卓,iOS 等,需要重新開發出不同的工具包來進行請求發送和相應解析,工作量大,難以快速響應和滿足用戶需求。
- 基于 HTTP 協議實現的 RPC 則可以使用 JSON 和 XML 格式的請求或響應數據,而 JSON 和 XML 作為通用的格式標準(使用 HTTP 協議也需要序列化和反序列化,不過這不是該協議下關心的內容,成熟的 Web 程序已經做好了序列化內容),開源的解析工具已經相當成熟,在其上進行二次開發會非常便捷和簡單。但是由于 HTTP 協議是上層協議,發送包含同等內容的信息,請求中會包含很多無用的內容,所占用的字節數比使用 TCP 協議傳輸更高,因此在同等網絡下,HTTP 會比基于 TCP 協議的數據傳輸效率要低,傳輸耗時更長,當然壓縮數據,能夠縮小這一差距。
三、RPC框架dubbo:
1、dubbo 是什么?
????????前面講到,RPC 常用于微服務架構中,而 RPC 框架作為架構微服務化的基礎組件,能大大降低架構微服務化的成本,提高調用方與服務提供方的研發效率。而 Dubbo 是阿里巴巴開源的基于 Java 的 RPC 分布式服務框架,提供高性能和透明化的 RPC 遠程服務調用方案,以及 SOA 服務治理方案。另外,基于 Spring Cloud Alibaba 技術棧的 Spring-cloud-alibaba-dubbo 更是對 dubbo 技術進行了封裝,在基于 Spring Cloud Alibaba 提供的 Nacos 注冊中心下,提供了 Dubbo 和 Spring Cloud 的整合方案,即 Dubbo Spring Cloud,使得服務內部的 RPC 協議調用幾乎是零成本的改造,實現了基于 RPC 的服務調用。
2、dubbo 的執行流程:
2.1、dubbo 總體流程:
- 紫色虛線:啟動時完成的功能
- 藍色虛線:運行過程中執行的功能,異步調用
- 藍色實線:運行過程中執行的功能,同步調用
2.1.1、dubbo 的總體執行流程說明如下:
(1)啟動容器,加載,運行服務提供者。
(2)服務提供者在啟動時,向注冊中心注冊自己提供的服務。
(3)服務消費者在啟動時,向注冊中心訂閱自己所需的服務。
(4)注冊中心返回服務提供者地址列表給消費者,消費者接收到之后,緩存在本地中,如果內容有變更,注冊中心將基于長連接推送變更數據給消費者。
(5)服務消費者,從提供者地址列表中,基于軟負載均衡算法,選擇一臺提供者進行調用,如果調用失敗,再選另一臺調用。
(6)服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心。
在consumer中使用了代理模式,創建了一個Provider類的一個代理對象。通過代理對象獲取Provider中的真實功能,起到保護Provider真實功能的作用。
2.1.2、dubbo 的整個執行流程可以理解為生產者-消費者模型+注冊中心+監控中心,這樣設計的原因在于:
- Consumer 與 Provider 解偶,雙方都可以橫向增減節點數
- 注冊中心對本身可做對等集群,可動態增減節點,并且任意一臺宕掉后,將自動切換到另一臺
- 去中心化,雙方不直接依懶注冊中心,即使注冊中心全部宕機短時間內也不會影響服務的調用
- 服務提供者無狀態,任意一臺宕掉后,不影響使用
2.2、dubbo 同步調用原理:
2.2.1、dubbo 同步調用流程:
- (1)客戶端線程調用遠程接口,向服務端發送請求,同時當前線程應該處于“暫停“狀態,即線程不能向后執行了,必需要拿到服務端給自己的結果后才能向后執行
- (2)服務端接到客戶端請求后,處理請求,將結果給客戶端
- (3)客戶端收到結果,然后當前線程繼續往后執行
????????dubbo 中使用了 Socket 來建立長連接、數據傳輸,而底層結合了的 Apache mina 框架,Apache mina 框架基于Reactor模型通信框架,基于tcp長連接。dubbo 使用 IoSession.write() 方法進行遠程調用與發送消息,這個方法的遠程調用過程異步的,即對于當前線程來說,將請求發送出來,線程就可以往后執行了,至于服務端的結果,是服務端處理完成后,再以消息的形式發送給客戶端的。于是這里出現了2個問題:
(1)當前線程怎么讓它“暫停”,等結果回來后,再向后執行:
????????先生成一個對象 obj,在一個全局 map 里 put(ID,obj) 存放起來,再用 synchronized 獲取 obj 鎖,再調用 obj.wait() 讓當前線程處于等待狀態,然后另一消息監聽線程等到服務端處理結果到來,再 map.get(ID) 找到 obj,再用 synchronized 獲取obj鎖,再調用 obj.notifyAll() 喚醒前面處于等待狀態的線程。
(2)Socket通信是一個全雙工的方式,當有多個線程同時進行遠程方法調用,這時 client 與 server 間的 socket 連接上會有很多雙方發送的消息傳遞,前后順序也可能是亂七八糟的,server處理完結果后,將結果消息發送給client,client收到很多消息,怎么知道哪個消息結果是原先哪個線程調用的:
????????使用一個ID,讓其唯一,然后傳遞給服務端,再服務端又回傳回來,這樣就知道結果是原先哪個線程的了
2.2.2、dubbo同步調用原理:
(1)客戶端使用一個線程調用遠程接口,生成一個唯一 ID,Dubbo 是使用 AtomicLong 從 0 開始累計數字的
(2)將打包的方法調用信息(如調用的接口名稱,方法名稱,參數值列表等),和處理結果的回調對象callback,全部封裝在一起,組成一個對象object
(3)向專門存放調用信息的全局 ConcurrentHashMap 里面 put(ID, object)
(4)將 ID 和打包的方法調用信息封裝成一對象 connRequest,使用 IoSession.write(connRequest) 異步發送出去
(5)當前線程再使用 callback 的 get() 方法試圖獲取遠程返回的結果,在get()內部,則先使用synchronized獲取回調對象callback的鎖, 檢測是否已經獲取到結果,如果沒有,然后調用 callback 的 wait() 方法,釋放 callback 上的鎖,讓當前線程處于等待狀態。
(6)服務端接收到請求并處理后,將結果(包含了唯一ID)回傳給客戶端,客戶端 socket 連接上專門監聽消息的線程收到消息后,分析結果,取到ID,再從前面的 ConcurrentHashMap 里面 get(ID),從而找到 callback,將方法調用結果設置到callback對象里。
(7)最后監聽線程再獲取回調對象 callback 的 synchronized 鎖(因為前面調用過wait() 導致釋放callback的鎖),先使用 notifyAll() 喚醒前面處于等待狀態的線程繼續執行,這樣 callback 的 get( )方法繼續執行就能拿到調用結果了,至此,整個過程結束
需要注意的是,這里的callback對象是每次調用產生一個新的,不能共享;另外ID必需至少保證在一個Socket連接里面是唯一的。
3、dubbo 的負載均衡策略:
dubbo 的負載均衡策略:https://www.cnblogs.com/wyq178/p/9822731.html
(1)隨機調用策略(默認):隨機選擇服務器節點,該策略可以對不同服務器實例設置不同的權重,權重越大分配流量越高
(2)輪詢調用策略:均勻地將請求分配到各個機器上。如果各個機器的性能不一樣,容易導致性能差的機器負載過高,所以此時需要調整權重,讓性能差的機器承載權重小一些,流量少一些。
(3)最少活躍數策略:根據服務器的運行狀態去選擇服務,如果某個機器性能越差,那么接收的請求越少,越不活躍,此時就會給不活躍的性能差的機器分配更少的請求
(4)一致性哈希算法:相同參數的請求一定會被分發到固定的服務器節點。當某個服務器節點掛掉的時候,會基于虛擬節點均勻分配剩余的流量,抖動不會太大。
4、dubbo 的容錯機制:
Failover(默認):失敗自動切換,當出現失敗,重試其它服務器,默認為2次。通常用于讀操作,但重試會帶來更長延遲。
Failfast:快速失敗,只發起一次調用,失敗立即報錯。通常用于非冪等性的寫操作,比如新增記錄。
Failsafe:失敗安全,出現異常時,直接忽略。通常用于寫入審計日志等操作。
Failback:失敗自動恢復,后臺記錄失敗請求,定時重發。通常用于消息通知操作。
Forking:并行調用多個服務器,只要一個成功即返回。通常用于實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks=”2″ 來設置最大并行數。
Broadcast:廣播調用所有提供者,逐個調用,任意一臺報錯則報錯 。通常用于通知所有提供者更新緩存或日志等本地資源信息。
5、dubbo支持哪些協議和適用場景:
(1)dubbo:單一長連接和 NIO 異步通訊,適合大并發小數據量的服務調用,以及消費者遠大于提供者的情況。傳輸協議 TCP,異步 Hessian 序列化。Dubbo 官方推薦使用 dubbo 協議。但是,dubbo 協議不適合傳送大數據量的服務,比如傳文件,傳視頻等,除非請求量很低
(2)RMI: 采用 JDK 標準的 RMI 協議實現,使用 Java 標準序列化機制,傳輸參數和返回參數對象需要實現 Serializable 接口,使用阻塞式短連接,傳輸數據包大小混合,消費者和提供者個數差不多,可傳文件,傳輸協議 TCP。多個短連接,基于 TCP 協議傳輸,同步傳輸,適用常規的遠程服務調用和 RMI 互操作。在依賴低版本的 Common-Collections 包,Java 序列化存在安全漏洞。
(3)WebService:基于 WebService 的遠程調用協議,集成 CXF 實現,提供和原生 WebService 的互操作。多個短連接,基于 HTTP 傳輸,同步傳輸,適用系統集成和跨語言調用。
(4)HTTP: 基于 Http 表單提交的遠程調用協議,使用 Spring 的 HttpInvoke 實現。多個短連接,傳輸協議 HTTP,傳入參數大小混合,提供者個數多于消費者,需要給應用程序和瀏覽器 JS 調用。
(5)Hessian:集成 Hessian 服務,基于 HTTP 通訊,采用 Servlet 暴露服務,Dubbo 內嵌 Jetty 作為服務器時默認實現,提供與 Hession 服務互操作。多個短連接,同步 HTTP 傳輸,Hessian 序列化,傳入參數較大,提供者大于消費者,提供者壓力較大,可傳文件。
(6)Redis:基于 Redis 實現的RPC協議。
(7)Memcache:基于 Memcache實現的 RPC 協議。
6、dubbo 的通信框架:
dubbo 默認使用 Netty 作為通訊框架
7、dubbo的架構設計:
?7.1、圖例說明:
- 左邊淡藍背景的為服務消費方使用的接口,右邊淡綠色背景的為服務提供方使用的接口,位于中軸線上的為雙方都用到的接口。
- 圖中從下至上分為 10 層,各層均為單向依賴,右邊的黑色箭頭代表層之間的依賴關系,每一層都可以剝離上層被復用,其中,Service 和 Config 層為 API,其它各層均為 SPI。
- 圖中綠色小塊的為擴展接口,藍色小塊為實現類,圖中只顯示用于關聯各層的實現類。
- 圖中藍色虛線為初始化過程,即啟動時組裝鏈,紅色實線為方法調用過程,即運行時調時鏈,紫色三角箭頭為繼承,可以把子類看作父類的同一個節點,線上的文字為調用的方法。
7.2、各層說明:
(1)接口服務層(Service):該層與實際業務邏輯相關,根據 provider 和 consumer 的業務設計對應的接口和實現
(2)配置層(Config):對外配置接口,以 ServiceConfig 和 ReferenceConfig 為中心
(3)服務代理層(Proxy):服務接口透明代理,生成服務的客戶端 Stub 和 服務端的 Skeleton,以 ServiceProxy 為中心,擴展接口為 ProxyFactory
(4)服務注冊層(Registry):封裝服務地址的注冊和發現,以服務 URL 為中心,擴展接口為 RegistryFactory、Registry、RegistryService
(5)路由層(Cluster):封裝多個提供者的路由和負載均衡,并橋接注冊中心,以Invoker 為中心,擴展接口為 Cluster、Directory、Router 和 LoadBlancce
(6)監控層(Monitor):RPC 調用次數和調用時間監控,以 Statistics 為中心,擴展接口為 MonitorFactory、Monitor 和 MonitorService
(7)遠程調用層(Protocal):封裝 RPC 調用,以 Invocation 和 Result 為中心,擴展接口為 Protocal、Invoker 和 Exporter
(8)信息交換層(Exchange):封裝請求響應模式,同步轉異步。以 Request 和Response 為中心,擴展接口為 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer
(9)網絡 傳輸 層(Transport):抽象 mina 和 netty 為統一接口,以 Message 為中心,擴展接口為 Channel、Transporter、Client、Server 和 Codec
(10)數據序列化層(Serialize):可復用的一些工具,擴展接口為 Serialization、ObjectInput、ObjectOutput 和 ThreadPool
dubbo 的架構設計詳情推薦閱讀官方文檔:https://dubbo.apache.org/zh/docs/v2.7/dev/design/
參考文章:https://juejin.cn/post/6844904127076499463
總結
以上是生活随笔為你收集整理的什么是RPC?RPC框架dubbo的核心流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java IO篇:什么是 Proacto
- 下一篇: 四层LVS与七层Nginx负载均衡的区别