01 前言
什麼是RPC?它的原理是什麼?它有什麼特點?如果讓你實作一個RPC架構,你會如何是實作?帶着這些問題,開始今天的學習。
02 RPC概述
2.1 什麼是RPC ?
RPC是遠端過程調用(Remote Procedure Call)。 RPC 的主要功能目标是讓建構分布式計算(應用)更容易,在提供強大的遠端調用能力時不損失本地調用的語義簡潔性。為實作該目标,RPC 架構需提供一種透明調用機制,讓使用者不必顯式的區分本地調用和遠端調用。
2.2 優點
- 1、分布式設計
- 2、部署靈活
- 3、解構服務
- 4、擴充性強
2.3 有哪些RPC架構?
- Dubbo:國内最早開源的 RPC 架構,由阿裡巴巴公司開發并于 2011 年末對外開源,僅支援 Java 語言。
- Motan:微網誌内部使用的 RPC 架構,于 2016 年對外開源,僅支援 Java 語言。
- Tars:騰訊内部使用的 RPC 架構,于 2017 年對外開源,僅支援 C++ 語言。
- Spring Cloud:國外 Pivotal 公司 2014 年對外開源的 RPC 架構,提供了豐富的生态元件。
- gRPC:Google 于 2015 年對外開源的跨語言 RPC 架構,支援多種語言。
- Thrift:最初是由 Facebook 開發的内部系統跨語言的 RPC 架構,2007 年貢獻給了 Apache 基金,成為Apache 開源項目之一,支援多種語言。
2.4 特性
- 1、RPC架構一般使用長連結,不必每次通信都要3次握手,減少網絡開銷。
- 2、RPC架構一般都有注冊中心,有豐富的監控管理。釋出、下線接口、動态擴充等,對調用方來說是無感覺、統一化的操作協定私密,安全性較高
- 3、RPC 協定更簡單内容更小,效率更高,服務化架構、服務化治理,RPC架構是一個強力的支撐。
2.5 架構

2.6 調用流程
具體步驟:
- 服務消費者(client用戶端)通過本地調用的方式調用服務。
-
用戶端存根(client stub)接收到請求後負責将方法、入參等資訊序列化(組裝)成能夠進行網絡傳輸的消息
體。
- 用戶端存根(client stub)找到遠端的服務位址,并且将消息通過網絡發送給服務端。
- 服務端存根(server stub)收到消息後進行解碼(反序列化操作)。
- 服務端存根(server stub)根據解碼結果調用本地的服務進行相關處理。
- 本地服務執行具體業務邏輯并将處理結果傳回給服務端存根(server stub)。
- 服務端存根(server stub)将傳回結果重新打包成消息(序列化)并通過網絡發送至消費方。
- 用戶端存根(client stub)接收到消息,并進行解碼(反序列化)。
- 服務消費方得到最終結果。
涉及到的技術
-
動态代理
生成Client Stub(用戶端存根)和Server Stub(服務端存根)的時候需要用到java動态代理技術。
- 序列化 在網絡中,所有的資料都将會被轉化為位元組進行傳送,需要對這些參數進行序列化和反序列化操作。目前主流高效的開源序列化架構有Kryo、fastjson、Hessian、Protobuf等。
-
NIO通信
Java 提供了 NIO 的解決方案,Java 7 也提供了更優秀的 NIO.2 支援。可以采用Netty或者mina架構來解決NIO資料傳輸的問題。開源的RPC架構Dubbo就是采用NIO通信,內建支援netty、mina、grizzly。
-
服務注冊中心
通過注冊中心,讓用戶端連接配接調用服務端所釋出的服務。主流的注冊中心元件:Redis、Zookeeper、Consul、Etcd。Dubbo采用的是ZooKeeper提供服務注冊與發現功能。
-
負載均衡
在高并發的場景下,需要多個節點或叢集來提升整體吞吐能力。
-
健康檢查
健康檢查包括,用戶端心跳和服務端主動探測兩種方式。
03 RPC深入解析
3.1 序列化技術
(1)序列化作用
在網絡傳輸中,資料必須采用二進制形式, 是以在RPC調用過程中, 需要采用序列化技術,對入參對象和傳回值對象進行序列化與反序列化。
(2)如何序列化?
自定義二進制協定來實作序列化:
(3)序列化的處理要素
序列化的處理要素
- 解析效率:序列化協定應該首要考慮的因素,像xml/json解析起來比較耗時,需要解析doom樹,二進制自定義協定解析起來效率要快很多。
- 壓縮率:同樣一個對象,xml/json傳輸起來有大量的标簽備援資訊,資訊有效性低,二進制自定義協定占用的空間相對來說會小很多。
- 擴充性與相容性:是否能夠利于資訊的擴充,并且增加字段後舊版用戶端是否需要強制更新,這都是需要考慮的問題,在自定義二進制協定時候,要做好充分考慮設計。
- 可讀性與可調試性:xml/json的可讀性會比二進制協定好很多,并且通過網絡抓包是可以直接讀取,二進制則需要反序列化才能檢視其内容。
- 跨語言:有些序列化協定是與開發語言緊密相關的,例如dubbo的Hessian序列化協定就隻能支援Java的RPC調用。
- 通用性:xml/json非常通用,都有很好的第三方解析庫,各個語言解析起來都十分友善,二進制資料的處理方面也有Protobuf和Hessian等插件,在做設計的時候盡量做到較好的通用性。
(4)常用序列化技術
- 1、JDK原生序列化,通過實作Serializable接口。通過ObjectOutPutSream和ObjectInputStream對象進行序列化及反序列化.
- 2、JSON序列化。一般在HTTP協定的RPC架構通信中,會選擇JSON方式。JSON具有較好的擴充性、可讀性和通用性。但JSON序列化占用空間開銷較大,沒有JAVA的強類型區分,需要通過反射解決,解析效率和壓縮率都較差。如果對并發和性能要求較高,或者是傳輸資料量較大的場景,不建議采用JSON序列化方式。
- 3、Hessian2序列化。Hessian 是一個動态類型,二進制序列化,并且支援跨語言特性的序列化架構。Hessian 性能上要比 JDK、JSON 序列化高效很多,并且生成的位元組數也更小。有非常好的相容性和穩定性,是以 Hessian 更加适合作為 RPC 架構遠端通信的序列化協定。
...
User user = new User();
user.setName("laowang");
//user對象序列化處理
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(user);
output.flushBuffer();
byte[] data = bos.toByteArray();
bos.close();
//user對象反序列化處理
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Hessian2Input input = new Hessian2Input(bis);
User user = (User) input.readObject();
input.close();
System.out.println(user);
...
Hessian自身也存在一些缺陷,大家在使用過程中要注意:
- 1、對Linked系列對象不支援,比如LinkedHashMap、LinkedHashSet 等,但可以通過CollectionSerializer類修複。
- 2、Locale 類不支援,可以通過擴充 ContextSerializerFactory 類修複。
- 3、Byte/Short 在反序列化的時候會轉成 Integer。
3.2 動态代理
RPC的調用内部核心技術采用的就是動态代理。
(1)JDK動态代理如何實作?
public class JdkProxyTest {
/**
* 定義使用者的接口
*/
public interface User {
String job();
}
/**
* 實際的調用對象
*/
public static class Teacher {
public String invoke(){
return "i'm Teacher";
}
}
/**
* 建立JDK動态代理類
*/
public static class JDKProxy implements InvocationHandler {
private Object target;
JDKProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] paramValues) {
return ((Teacher)target).invoke();
}
}
public static void main(String[] args){
// 建構代理器
JDKProxy proxy = new JDKProxy(new Teacher());
ClassLoader classLoader = ClassLoaderUtils.getClassLoader();
// 生成代理類
User user = (User) Proxy.newProxyInstance(classLoader, new Class[]{User.class},
proxy);
// 接口調用
System.out.println(user.job());
}
}
(2)JDK動态代理實作原理:
代理類 $Proxy裡面會定義相同簽名的接口,然後内部會定義一個變量綁定JDKProxy代理對象,當調用User.job接口方法,實質上調用的是JDKProxy.invoke()方法。
3.3 服務注冊發現
(1)注冊與發現流程
服務注冊:服務提供方将對外暴露的接口釋出到注冊中心内,注冊中心為了檢測服務的有效狀态,一般會建立雙向心跳機制。
服務訂閱:服務調用方去注冊中心查找并訂閱服務提供方的 IP,并緩存到本地用于後續調用。
(2)如何實作:基于ZK
A. 在 ZooKeeper 中建立一個服務根路徑,可以根據接口名命名(例
如:/micro/service/com.laowang.orderService),在這個路徑再建立服務提供方與調用方目錄(server、
client),分别用來存儲服務提供方和調用方的節點資訊。
B. 服務端發起注冊時,會在服務提供方目錄中建立一個臨時節點,節點中存儲注冊資訊。
C. 用戶端發起訂閱時,會在服務調用方目錄中建立一個臨時節點,節點中存儲調用方的資訊,同時watch 服務提供方的目錄(/micro/service/com.laowang.orderService/server)中所有的服務節點資料。當服務端産生變化時ZK就會通知給訂閱的用戶端。
ZooKeeper方案的特點:
強一緻性,ZooKeeper 叢集的每個節點的資料每次發生更新操作,都會通知其它 ZooKeeper 節點同時執行更新。
3.4 健康監測
為什麼需要做健康監測?
比如網絡中的波動,硬體設施的老化等等。可能造成叢集當中的某個節點存在問題,無法正常調用。
健康監測實作分析
心跳檢測的過程總共包含以下狀态:健康狀态、波動狀态、失敗狀态。
完善的解決方案
(1)門檻值: 健康監測增加失敗門檻值記錄。
(2)成功率: 可以再追加調用成功率的記錄(成功次數/總次數)。
(3)探針: 對服務節點有一個主動的存活檢測機制。
3.5 網絡IO模型
3.6 零拷貝
(1)什麼是零拷貝?
系統核心處理 IO 操作分為兩個階段:等待資料和拷貝資料。
等待資料,就是系統核心在等待網卡接收到資料後,把資料寫到核心中。
拷貝資料,就是系統核心在擷取到資料後,将資料拷貝到使用者程序的空間中
所謂的零拷貝,就是取消使用者空間與核心空間之間的資料拷貝操作,應用程序每一次的讀寫操作,都可以通過一種方式,讓應用程序向使用者空間寫入或者讀取資料,就如同直接向核心空間寫入或者讀取資料一樣,再通過 DMA 将核心中的資料拷貝到網卡,或将網卡中的資料 copy 到核心。
(2)RPC架構的零拷貝應用
Netty 架構是否也有零拷貝機制?
Netty 的零拷貝則有些不一樣,他完全站在了使用者空間上,也就是基于 JVM 之上。
Netty當中的零拷貝是如何實作的?
RPC 并不會把請求參數作為一個整體資料包發送到對端機器上,中間可能會拆分,也可能會合并其他請求,是以消息都需要有邊界。接收到消息之後,需要對資料包進行處理,根據邊界對資料包進行分割和合并,最終獲得完整的消息。
Netty零拷貝主要展現在三個方面:
1、Netty的接收和發送ByteBuffer是采用DIRECT BUFFERS,使用堆外的直接記憶體(記憶體對象配置設定在JVM中堆以外的記憶體)進行Socket讀寫,不需要進行位元組緩沖區的二次拷貝。如果采用傳統堆記憶體(HEAP BUFFERS)進行Socket讀寫,JVM會将堆記憶體Buffer拷貝一份到直接記憶體中,然後寫入Socket中。
2、Netty提供了組合Buffer對象,也就是CompositeByteBuf 類,可以将 ByteBuf 分解為多個共享同一個存儲區域的 ByteBuf,避免了記憶體的拷貝。
3、Netty的檔案傳輸采用了FileRegion 中包裝 NIO 的 FileChannel.transferT o() 方法,它可以直接将檔案緩沖區的資料發送到目标Channel,避免了傳統通過循環write方式導緻的記憶體拷貝問題。
零拷貝帶來的作用就是避免沒必要的 CPU 拷貝,減少了 CPU 在使用者空間與核心空間之間的上下文切換,進而提升了網絡通信效率與應用程式的整體性能。
3.7 時間輪
(1)為什麼需要時間輪?
在Dubbo中,為增強系統的容錯能力,會有相應的監聽判斷處理機制。比如RPC調用的逾時機制的實作,消費者判斷RPC調用是否逾時,如果逾時會将逾時結果傳回給應用層。在Dubbo最開始的實作中,是将所有的傳回結果(DefaultFuture)都放入一個集合中,并且通過一個定時任務,每隔一定時間間隔就掃描所有的future,逐個判斷是否逾時。
這樣的實作方式雖然比較簡單,但是存在一個問題就是會有很多無意義的周遊操作開銷。比如一個RPC調用的逾時時間是10秒,而設定的逾時判定的定時任務是2秒執行一次,那麼可能會有4次左右無意義的循環檢測判斷操作。
為了解決上述場景中的類似問題,Dubbo借鑒Netty,引入了時間輪算法,減少無意義的輪詢判斷操作。
(2)時間輪原理
對于以上問題, 目的是要減少額外的掃描操作就可以了。比如說一個定時任務是在5 秒之後執行,那麼在 4.9秒之後才掃描這個定時任務,這樣就可以極大減少 CPU開銷。這時我們就可以利用時鐘輪的機制了。
時鐘輪的實質上是參考了生活中的時鐘跳動的原理,那麼具體是如何實作呢?
在時鐘輪機制中,有時間槽和時鐘輪的概念,時間槽就相當于時鐘的刻度;而時鐘輪就相當于指針跳動的一個周期,我們可以将每個任務放到對應的時間槽位上。
如果時鐘輪有 10 個槽位,而時鐘輪一輪的周期是 10 秒,那麼我們每個槽位的機關時間就是 1 秒,而下一層時間輪的周期就是 100 秒,每個槽位的機關時間也就是 10 秒,這就好比秒針與分針, 在秒針周期下, 刻度機關為
秒, 在分針周期下, 刻度為分。
假設現在我們有 3 個任務,分别是任務 A(0.9秒之後執行)、任務 B(2.1秒後執行)與任務 C(12.1秒之後執
行),我們将這 3 個任務添加到時鐘輪中,任務 A 被放到第 0 槽位,任務 B 被放到第 2槽位,任務 C 被放到下一
層時間輪的第2個槽位。
通過這個場景我們可以了解到,時鐘輪的掃描周期仍是最小機關1秒,但是放置其中的任務并沒有反複掃描,每個
任務會按要求隻掃描執行一次, 這樣就能夠很好的解決CPU 浪費的問題。
(3)Dubbo中的時間輪原理是如何實作的?
主要是通過Timer,Timeout,TimerT ask幾個接口定義了一個定時器的模型,再通過HashedWheelTimer這個類實作了一個時間輪定時器(預設的時間槽的數量是512,可以自定義這個值)。它對外提供了簡單易用的接口,隻需要調用newTimeout接口,就可以實作對隻需執行一次任務的排程。通過該定時器,Dubbo在響應的場景中實作了高效的任務排程。
(4)時間輪在RPC的應用
調用逾時: 上面所講的用戶端調用逾時的處理,就可以應用到時鐘輪,我們每發一次請求,都建立一個處理請求逾時的定時任務放到時鐘輪裡,在高并發、高通路量的情況下,時鐘輪每次隻輪詢一個時間槽位中的任務,這樣會節省大量的 CPU。
啟動加載: 調用端與服務端啟動也可以應用到時鐘輪,比如說在服務啟動完成之後要去加載緩存,執行定時任務等, 都可以放在時鐘輪裡
定時心跳檢測: RPC 架構調用端定時向服務端發送的心跳檢測,來維護連接配接狀态,我們可以将心跳的邏輯封裝為一個心跳任務,放到時鐘輪裡。心跳是要定時重複執行的,而時鐘輪中的任務執行一遍就被移除了,對于這種需要重複執行的定時任務我們該如何處理呢?我們在定時任務邏輯結束的最後,再加上一段邏輯, 重設這個任務的執行時間,把它重新丢回到時鐘輪裡。這樣就可以實作循環執行。
04 PRC進階應用
4.1 異步處理機制
(1)為什麼要采用異步?
如果采用同步調用, CPU 大部分的時間都在等待而沒有去計算,進而導緻 CPU 的使用率不夠。RPC 請求比較耗時的原因主要是在哪裡?在大多數情況下,RPC 本身處理請求的效率是在毫秒級的。RPC 請求的耗時大部分都是業務耗時。
(2)調用端如何實作異步
常用的方式就是Future 方式,它是傳回 Future 對象,通過GET方式擷取結果;或者采用入參為 Callback 對象的回調方式,處理結果。
基于RPC的DUBBO架構是如何實作異步調用呢?
(3)服務端如何實作異步?
為了提升性能,連接配接請求與業務處理不會放在一個線程處理, 這個就是服務端的異步化。服務端業務處理邏輯加入異步處理機制。在RPC 架構提供一種回調方式,讓業務邏輯可以異步處理,處理完之後調用 RPC 架構的回調接口
RPC 架構的異步政策主要是調用端異步與服務端異步。調用端的異步就是通過 Future 方式。
服務端異步則需要一種回調方式,讓業務邏輯可以異步處理。這樣就實作了RPC調用的全異步化
4.2 路由和負載均衡
(1)為什麼要用路由
真實的環境中一般是以叢集的方式提供服務,對于服務調用方來說,一個接口會有多個服務提供方同時提供服務,是以 RPC 在每次發起請求的時候,都需要從多個服務節點裡面選取一個用于處理請求的服務節點。這就需要在RPC應用中增加路由功能。
(2)如何實作路由
服務注冊發現方式:
通過服務發現的方式從邏輯上看是可行,但注冊中心是用來保證資料的一緻性。通過服務發現方式來實作請求隔離并不理想。
RPC路由政策:
從服務提供方節點集合裡面選擇一個合适的節點(負載均衡),把符合我們要求的節點篩選出來。這個就是路由政策:接收請求-->請求校驗-->路由政策-->負載均衡-->
有些場景下,可能還需要更細粒度的路由方式,比如說根據SESSIONID要落到相同的服務節點上以保持會話的有效性;
(3)RPC架構中的負載均衡
RPC 的負載均衡是由 RPC 架構自身提供實作,自主選擇一個最佳的服務節點,發起 RPC 調用請求。
RPC 負載均衡政策一般包括輪詢、随機、權重、最少連接配接等。Dubbo預設就是使用随機負載均衡政策。
自适應的負載均衡政策
RPC 的負載均衡完全由 RPC 架構自身實作,通過所配置的負載均衡元件,自主選擇合适服務節點。這個就是自适應的負載均衡政策。
具體如何實作?
這就需要判定服務節點的處理能力。
主要步驟:
(1)添加計分器和名額采集器。
(2)名額采集器收集服務節點 CPU 核數、CPU 負載以及記憶體占用率等名額。
(3)可以配置開啟哪些名額采集器,并設定這些參考名額的具體權重。
(4)通過對服務節點的綜合打分,最終計算出服務節點的實際權重,選擇合适的服務節點。
4.3 熔斷限流
在實際生産環境中,每個服務節點都可能由于通路量過大而引起一系列問題,就需要業務提供方能夠進行自我保護,進而保證在高通路量、高并發的場景下,系統依然能夠穩定,高效運作。
在Dubbo架構中, 可以通過Sentinel來實作更為完善的熔斷限流功能,服務端是具體如何實作限流邏輯的?
方法有很多種, 最簡單的是計數器,還有平滑限流的滑動視窗、漏鬥算法以及令牌桶算法等等。Sentinel采用是滑動視窗來實作的限流。
調用方的自我保護
一個服務 A 調用服務 B 時,服務 B 的業務邏輯又調用了服務 C,這時服務 C 響應逾時,服務 B 就可能會因為堆積大量請求而導緻服務當機,由此産生服務雪崩的問題。
熔斷機制:
熔斷器的工作機制主要是關閉、打開和半打開這三個狀态之間的切換。
Sentinel 熔斷降級元件它可以支援以下降級政策:
平均響應時間 ( DEGRADE_GRADE_RT ):當 1s 内持續進入 N 個請求,對應時刻的平均響應時間(秒級)均超過門檻值( count ,以 ms 為機關),那麼在接下的時間視窗( DegradeRule 中的 timeWindow ,以 s為機關)之内,對這個方法的調用都會自動地熔斷(抛出 DegradeException )。注意 Sentinel 預設統計的 RT 上限是 4900 ms,超出此門檻值的都會算作 4900 ms,若需要變更此上限可以通過啟動配置項Dcsp.sentinel.statistic.max.rt=xxx 來配置。
異常比例 ( DEGRADE_GRADE_EXCEPTION_RATIO ):當資源的每秒請求量 >= N(可配置),并且每秒異常總數占通過量的比值超過門檻值( DegradeRule 中的 count )之後,資源進入降級狀态,即在接下的時間視窗( DegradeRule 中的 timeWindow ,以 s 為機關)之内,對這個方法的調用都會自動地傳回。異常比率的門檻值範圍是 [0.0, 1.0] ,代表 0% - 100%。
異常數 ( DEGRADE_GRADE_EXCEPTION_COUNT ):當資源近 1 分鐘的異常數目超過門檻值之後會進行熔斷。注意由于統計時間視窗是分鐘級别的,若 timeWindow 小于 60s,則結束熔斷狀态後仍可能再進入熔斷狀态。
4.4 優雅啟動
(1)什麼是啟動預熱?
啟動預熱就是讓剛啟動的服務,不直接承擔全部的流量,而是讓它随着時間的移動慢慢增加調用次數,最終讓流量緩和運作一段時間後達到正常水準。
(2)如何實作?
首先要知道服務提供方的啟動時間,有兩種擷取方法:
一種是服務提供方在啟動的時候,主動将啟動的時間發送給注冊中心;
另一種就是注冊中心來檢測, 将服務提供方的請求注冊時間作為啟動時間。
調用方通過服務發現擷取服務提供方的啟動時間, 然後進行降級,減少被負載均衡選擇的機率,進而實作預熱過程。
在Dubbo架構中也引入了"warmup"特性,核心源碼是
在" com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance.java"中:
protected int getWeight(Invoker<?> invoker, Invocation invocation) {
// 先得到Provider的權重
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(),
Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
if (weight > 0) {
// 得到provider的啟動時間戳
long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
// provider已經運作時間
int uptime = (int) (System.currentTimeMillis() ‐ timestamp);
// 得到warmup的值,預設為10分鐘
int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY,
Constants.DEFAULT_WARMUP);
// provider運作時間少于預熱時間,那麼需要重新計算權重weight(即需要降級)
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight(uptime, warmup, weight);
}
}
}
return weight;
}
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
// 随着provider的啟動時間越來越長,慢慢提升權重weight
int ww = (int) ( (float) uptime / ( (float) warmup / (float) weight ) );
return ww < 1 ? 1 : (ww > weight ? weight : ww);
}
4.5 優雅關閉
(1)為什麼需要優雅關閉?
調用方會存在以下情況:目标服務已經下線;目标服務正在關閉中。
(2)如何實作優雅關閉?
當服務提供方正在關閉,可以直接傳回一個特定的異常給調用方。然後調用方把這個節點從健康清單挪出,并把其
他請求自動重試到其他節點。如需更為完善, 可以再加上主動通知機制。
在Dubbo架構中, 在以下場景中會觸發優雅關閉:
JVM主動關閉( System.exit(int) ; JVM由于資源問題退出( OOM ); 應用程式接受到程序正常結束信号:SIGTERM 或 SIGINT 信号。
優雅停機是預設開啟的,停機等待時間為10秒。可以通過配置 dubbo.service.shutdown.wait 來修改等待時間。Dubbo 推出了多段關閉的方式來保證服務完全無損。
05 如何實作一個RPC架構?
思路:
-
服務設計:用戶端、服務端、ZK注冊中心,擷取訂單接口。
怎麼知道服務端的資訊? 如何去調用的?
- 先啟動服務端: 将接口資訊注冊至ZK。(ServicePushManager.registerIntoZK方法)
- 啟動用戶端: 從ZK拉取服務端接口資訊。(ServicePullManager.pullServiceFromZK方法)
-
Rpc調用處理流程:
用戶端->通過動态代理調用服務端接口(ProxyHelper.doIntercept)-> 選取不同的調用政策-> 異步方式調用(通過MAP存儲記錄channel,rpcRequestPool.fetchResponse擷取結果)-> 服務端(根據請求資訊調用對應的接口, RpcRequestHandler.channelRead0)-> 用戶端監聽接收結果(RpcResponseHandler.channelRead0)-> 關閉連接配接(RpcRequestManager.destroyChannelHolder關閉連接配接)