天天看點

什麼是RPC?原理是什麼?如何實作一個 RPC 架構?01 前言02 RPC概述03 RPC深入解析04 PRC進階應用05 如何實作一個RPC架構?

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 架構

什麼是RPC?原理是什麼?如何實作一個 RPC 架構?01 前言02 RPC概述03 RPC深入解析04 PRC進階應用05 如何實作一個RPC架構?

2.6 調用流程

什麼是RPC?原理是什麼?如何實作一個 RPC 架構?01 前言02 RPC概述03 RPC深入解析04 PRC進階應用05 如何實作一個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. 服務消費方得到最終結果。

涉及到的技術

  1. 動态代理

    生成Client Stub(用戶端存根)和Server Stub(服務端存根)的時候需要用到java動态代理技術。

  2. 序列化 在網絡中,所有的資料都将會被轉化為位元組進行傳送,需要對這些參數進行序列化和反序列化操作。目前主流高效的開源序列化架構有Kryo、fastjson、Hessian、Protobuf等。
  3. NIO通信

    Java 提供了 NIO 的解決方案,Java 7 也提供了更優秀的 NIO.2 支援。可以采用Netty或者mina架構來解決NIO資料傳輸的問題。開源的RPC架構Dubbo就是采用NIO通信,內建支援netty、mina、grizzly。

  4. 服務注冊中心

    通過注冊中心,讓用戶端連接配接調用服務端所釋出的服務。主流的注冊中心元件:Redis、Zookeeper、Consul、Etcd。Dubbo采用的是ZooKeeper提供服務注冊與發現功能。

  5. 負載均衡

    在高并發的場景下,需要多個節點或叢集來提升整體吞吐能力。

  6. 健康檢查

    健康檢查包括,用戶端心跳和服務端主動探測兩種方式。

03 RPC深入解析

3.1 序列化技術

(1)序列化作用

在網絡傳輸中,資料必須采用二進制形式, 是以在RPC調用過程中, 需要采用序列化技術,對入參對象和傳回值對象進行序列化與反序列化。

(2)如何序列化?

自定義二進制協定來實作序列化:

什麼是RPC?原理是什麼?如何實作一個 RPC 架構?01 前言02 RPC概述03 RPC深入解析04 PRC進階應用05 如何實作一個RPC架構?

(3)序列化的處理要素

序列化的處理要素

  1. 解析效率:序列化協定應該首要考慮的因素,像xml/json解析起來比較耗時,需要解析doom樹,二進制自定義協定解析起來效率要快很多。
  2. 壓縮率:同樣一個對象,xml/json傳輸起來有大量的标簽備援資訊,資訊有效性低,二進制自定義協定占用的空間相對來說會小很多。
  3. 擴充性與相容性:是否能夠利于資訊的擴充,并且增加字段後舊版用戶端是否需要強制更新,這都是需要考慮的問題,在自定義二進制協定時候,要做好充分考慮設計。
  4. 可讀性與可調試性:xml/json的可讀性會比二進制協定好很多,并且通過網絡抓包是可以直接讀取,二進制則需要反序列化才能檢視其内容。
  5. 跨語言:有些序列化協定是與開發語言緊密相關的,例如dubbo的Hessian序列化協定就隻能支援Java的RPC調用。
  6. 通用性: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動态代理實作原理:

什麼是RPC?原理是什麼?如何實作一個 RPC 架構?01 前言02 RPC概述03 RPC深入解析04 PRC進階應用05 如何實作一個RPC架構?

代理類 $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架構是如何實作異步調用呢?

什麼是RPC?原理是什麼?如何實作一個 RPC 架構?01 前言02 RPC概述03 RPC深入解析04 PRC進階應用05 如何實作一個RPC架構?

(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架構?

思路:

  1. 服務設計:用戶端、服務端、ZK注冊中心,擷取訂單接口。

    怎麼知道服務端的資訊? 如何去調用的?

  2. 先啟動服務端: 将接口資訊注冊至ZK。(ServicePushManager.registerIntoZK方法)
  3. 啟動用戶端: 從ZK拉取服務端接口資訊。(ServicePullManager.pullServiceFromZK方法)
  4. Rpc調用處理流程:

    用戶端->通過動态代理調用服務端接口(ProxyHelper.doIntercept)-> 選取不同的調用政策-> 異步方式調用(通過MAP存儲記錄channel,rpcRequestPool.fetchResponse擷取結果)-> 服務端(根據請求資訊調用對應的接口, RpcRequestHandler.channelRead0)-> 用戶端監聽接收結果(RpcResponseHandler.channelRead0)-> 關閉連接配接(RpcRequestManager.destroyChannelHolder關閉連接配接)