高性能可擴(kuò)展分布式RPC框架Dubbo-內(nèi)核原理揭秘

一、前言

整體來說,一個(gè)公司業(yè)務(wù)系統(tǒng)的演進(jìn)流程基本都是從單體應(yīng)用到多體應(yīng)用。在單體應(yīng)用時(shí),不同業(yè)務(wù)模塊相互調(diào)用直接在本地 JVM 進(jìn)程內(nèi)就可以完成;而變?yōu)槎鄠€(gè)應(yīng)用時(shí),相互之間進(jìn)行通信的方式就不能簡單的進(jìn)行本地調(diào)用了,因?yàn)椴煌瑯I(yè)務(wù)模塊部署到了不同的 JVM 進(jìn)程里面,更常見的是部署到了不同的機(jī)器,這時(shí)候一個(gè)高效、穩(wěn)定的 RPC 遠(yuǎn)程調(diào)用框架就變得非常重要。

Dubbo作為阿里巴巴開發(fā)的一個(gè)開源的高性能的RPC調(diào)用框架,其致力于提供高性能和透明化的 RPC 遠(yuǎn)程調(diào)用服務(wù)解決方案。作為阿里巴巴 SOA 服務(wù)化治理方案的核心框架,目前它已進(jìn)入 Apache 孵化器頂級(jí)項(xiàng)目,前景可謂無限光明。

二、Dubbo-基礎(chǔ)篇

2.1 Dubbo系統(tǒng)組成概述

使用Dubbo框架搭建的系統(tǒng)架構(gòu)如下:


image.png

如上圖是 Dubbo 的架構(gòu)圖,其中:

  • 服務(wù)提供方在啟動(dòng)時(shí)候會(huì)注冊(cè)自己提供的服務(wù)到服務(wù)注冊(cè)中心。

  • 服務(wù)消費(fèi)方在啟動(dòng)時(shí)候會(huì)去服務(wù)注冊(cè)中心訂閱自己需要的服務(wù)的地址列表,然后服務(wù)注冊(cè)中心異步把消費(fèi)方需要的服務(wù)接口的提供者的地址列表返回給服務(wù)消費(fèi)方,服務(wù)消費(fèi)方根據(jù)路由規(guī)則和設(shè)置的負(fù)載均衡算法選擇一個(gè)服務(wù)提供者 IP 進(jìn)行調(diào)用。

  • 監(jiān)控平臺(tái)主要用來統(tǒng)計(jì)服務(wù)的調(diào)用次數(shù)和調(diào)用耗時(shí),服務(wù)消費(fèi)者和提供者,在內(nèi)存中累計(jì)調(diào)用次數(shù)和調(diào)用耗時(shí),并定時(shí)每分鐘發(fā)送一次統(tǒng)計(jì)數(shù)據(jù)到監(jiān)控中心,監(jiān)控中心則使用數(shù)據(jù)繪制圖表來顯示,監(jiān)控平臺(tái)不是分布式系統(tǒng)必須的,但是這些數(shù)據(jù)有助于系統(tǒng)運(yùn)維和調(diào)優(yōu)。服務(wù)提供者和消費(fèi)者可以直接配置監(jiān)控平臺(tái)的地址,也可以通過服務(wù)注冊(cè)中心來獲取。

  • 服務(wù)注冊(cè)中心則負(fù)責(zé)服務(wù)注冊(cè)與發(fā)現(xiàn),常見的服務(wù)注冊(cè)中心有zookeeper、etcd。

2.2 Dubbo基礎(chǔ)

本節(jié)主要簡單的講解Dubbo如何使用,以及本書中的demo實(shí)例,建議讀者先閱讀基礎(chǔ)篇在進(jìn)入后面的章節(jié),因?yàn)楹竺嬲鹿?jié)基本是基于本章的demo進(jìn)行講解的。

demo中 Consumer 模塊為服務(wù)消費(fèi)者相關(guān),本書中所有與消費(fèi)端有關(guān)的demo都在該模塊中,包含普通調(diào)用、各種異步調(diào)用、泛化調(diào)用、基于擴(kuò)展接口實(shí)現(xiàn)的自定義負(fù)載均衡策略、集群容錯(cuò)策略等等。

其中 Provider 模塊為服務(wù)提供者相關(guān),本書中所有與服務(wù)提供端有關(guān)的demo都在該模塊中,包含服務(wù)接口的實(shí)現(xiàn)類、服務(wù)提供方的同步處理請(qǐng)求、各種異步處理請(qǐng)求的實(shí)現(xiàn)等等。

其中 SDK 模塊是一個(gè)二方包,用來存放服務(wù)接口,這是為了代碼復(fù)用,在服務(wù)提供者和消費(fèi)者(泛化調(diào)用除外)的模塊里面都需要引入這個(gè)二方包。

三、Dubbo-高級(jí)篇

3.1 Dubbo分層架構(gòu)

本節(jié)我們從整體上來看看 Dubbo 的分層架構(gòu)設(shè)計(jì),架構(gòu)分層是一個(gè)比較經(jīng)典的模式,比如網(wǎng)絡(luò)中的7層協(xié)議,每層執(zhí)行固定的功能,上層依賴下層提供的功能,下層對(duì)上層的提供功能,下層的改變對(duì)上層不可見,并且每層都是一個(gè)可被替換的組件。

如下圖是 Dubbo 官方提供的Dubbo的整體架構(gòu)圖:

image.png

Dubbo 官方提供的該架構(gòu)圖很復(fù)雜,一開始我們沒必要深入細(xì)節(jié),下面我們簡單講解下其中的主要模塊:

  • 其中 Service 和 Config 層為 API接口層,是為了方便的讓Dubbo使用方發(fā)布服務(wù)和引用服務(wù);對(duì)于服務(wù)提供方來說需要實(shí)現(xiàn)服務(wù)接口,然后使用 ServiceConfig API 來發(fā)布該服務(wù);對(duì)于服務(wù)消費(fèi)方來說需要使用ReferenceConfig 對(duì)服務(wù)接口進(jìn)行代理。Dubbo服務(wù)發(fā)布與引用方可以直接初始化配置類,也可以通過 Spring 配置自動(dòng)生成配置類。

  • 其它各層均為 SPI層,SPI 意味著下面各層都是組件化可以被替換的,這也是 Dubbo 設(shè)計(jì)的比較好的一點(diǎn)。Dubbo 增強(qiáng)了 JDK 中提供的標(biāo)準(zhǔn) SPI 功能,在 Dubbo 中除了 Service 和 Config 層外,其它各層都是通過實(shí)現(xiàn)擴(kuò)展點(diǎn)接口來提供服務(wù)的;Dubbo 增強(qiáng)的 SPI 增加了對(duì)擴(kuò)展點(diǎn) IoC 和 AOP 的支持,一個(gè)擴(kuò)展點(diǎn)可以直接 setter 注入其它擴(kuò)展點(diǎn);并且不會(huì)一次性實(shí)例化擴(kuò)展點(diǎn)的所有實(shí)現(xiàn)類,這避免了當(dāng)擴(kuò)展點(diǎn)實(shí)現(xiàn)類初始化很耗時(shí),但當(dāng)前還沒用上它的功能時(shí)仍進(jìn)行加載實(shí)例化,浪費(fèi)資源的情況;增強(qiáng)的 SPI 是在具體用某一個(gè)實(shí)現(xiàn)類的時(shí)候才對(duì)具體實(shí)現(xiàn)類進(jìn)行實(shí)例化。后續(xù)會(huì)具體講解 Dubbo 增強(qiáng)的 SPI 的實(shí)現(xiàn)原理。

  • Proxy 服務(wù)代理層:該層主要是對(duì)服務(wù)消費(fèi)端使用的接口進(jìn)行代理,把本地調(diào)用透明的轉(zhuǎn)換為遠(yuǎn)程調(diào)用;另外對(duì)服務(wù)提供方的服務(wù)實(shí)現(xiàn)類進(jìn)行代理,把服務(wù)實(shí)現(xiàn)類轉(zhuǎn)換為 Wrapper 類,這是為了減少反射的調(diào)用,后面會(huì)具體講解到。Proxy層的SPI擴(kuò)展接口為 ProxyFactory,Dubbo 提供的實(shí)現(xiàn)主要有 JavassistProxyFactory(默認(rèn)使用)和 JdkProxyFactory,用戶可以實(shí)現(xiàn)ProxyFactory SPI接口,自定義代理服務(wù)層的實(shí)現(xiàn)。

  • Registry 服務(wù)注冊(cè)中心層:服務(wù)提供者啟動(dòng)時(shí)候會(huì)把服務(wù)注冊(cè)到服務(wù)注冊(cè)中心,消費(fèi)者啟動(dòng)時(shí)候會(huì)去服務(wù)注冊(cè)中心獲取服務(wù)提供者的地址列表,Registry層主要功能是封裝服務(wù)地址的注冊(cè)與發(fā)現(xiàn)邏輯,擴(kuò)展接口 Registry 對(duì)應(yīng)的擴(kuò)展實(shí)現(xiàn)為 ZookeeperRegistry、RedisRegistry、MulticastRegistry、DubboRegistry等。擴(kuò)展接口 RegistryFactory 對(duì)應(yīng)的擴(kuò)展接口實(shí)現(xiàn)為 DubboRegistryFactory、DubboRegistryFactory、RedisRegistryFactory、ZookeeperRegistryFactory。另外該層擴(kuò)展接口Directory實(shí)現(xiàn)類有RegistryDirectory、StaticDirectory用來透明的把invoker列表轉(zhuǎn)換為一個(gè)invoker;用戶可以實(shí)現(xiàn)該層的一系列擴(kuò)展接口,自定義該層的服務(wù)實(shí)現(xiàn)。

  • Cluster 路由層:封裝多個(gè)服務(wù)提供者的路由規(guī)則、負(fù)載均衡、集群容錯(cuò)的實(shí)現(xiàn),并橋接服務(wù)注冊(cè)中心;擴(kuò)展接口 Cluster 對(duì)應(yīng)的實(shí)現(xiàn)類有 FailoverCluster(失敗重試)、FailbackCluster(失敗自動(dòng)恢復(fù))、FailfastCluster(快速失?。?、FailsafeCluster(失敗安全)、ForkingCluster(并行調(diào)用)等;負(fù)載均衡擴(kuò)展接口 LoadBalance 對(duì)應(yīng)的實(shí)現(xiàn)類為 RandomLoadBalance(隨機(jī))、RoundRobinLoadBalance(輪詢)、LeastActiveLoadBalance(最小活躍數(shù))、ConsistentHashLoadBalance(一致性hash)等。用戶可以實(shí)現(xiàn)該層的一系列擴(kuò)展接口,自定義集群容錯(cuò)和負(fù)載均衡策略。

  • Monitor 監(jiān)控層:用來統(tǒng)計(jì)RPC 調(diào)用次數(shù)和調(diào)用耗時(shí)時(shí)間,擴(kuò)展接口為 MonitorFactory,對(duì)應(yīng)的實(shí)現(xiàn)類為 DubboMonitorFactroy。用戶可以實(shí)現(xiàn)該層的MonitorFactory擴(kuò)展接口,實(shí)現(xiàn)自定義監(jiān)控統(tǒng)計(jì)策略。

  • Protocol 遠(yuǎn)程調(diào)用層:封裝 RPC 調(diào)用邏輯,擴(kuò)展接口為 Protocol, 對(duì)應(yīng)實(shí)現(xiàn)有 RegistryProtocol、DubboProtocol、InjvmProtocol 等。

  • Exchange 信息交換層:封裝請(qǐng)求響應(yīng)模式,同步轉(zhuǎn)異步,擴(kuò)展接口 Exchanger,對(duì)應(yīng)擴(kuò)展實(shí)現(xiàn)有 HeaderExchanger 等。

  • Transport 網(wǎng)絡(luò)傳輸層:抽象 mina 和 netty 為統(tǒng)一接口。擴(kuò)展接口為 Channel,對(duì)應(yīng)實(shí)現(xiàn)有 NettyChannel(默認(rèn))、MinaChannel 等;擴(kuò)展接口Transporter對(duì)應(yīng)的實(shí)現(xiàn)類有GrizzlyTransporter、MinaTransporter、NettyTransporter(默認(rèn)實(shí)現(xiàn));擴(kuò)展接口Codec2對(duì)應(yīng)實(shí)現(xiàn)類有DubboCodec、ThriftCodec等

  • Serialize 數(shù)據(jù)序列化層:提供可以復(fù)用的一些工具,擴(kuò)展接口為 Serialization,對(duì)應(yīng)擴(kuò)展實(shí)現(xiàn)有 DubboSerialization、FastJsonSerialization、Hessian2Serialization、JavaSerialization等,擴(kuò)展接口ThreadPool對(duì)應(yīng)擴(kuò)展實(shí)現(xiàn)有 FixedThreadPool、CachedThreadPool、LimitedThreadPool 等。

綜上可知Dubbo的分層架構(gòu)使得Dubbo的每層的功能都是可被替換的,這使得Dubbo的擴(kuò)展性極強(qiáng),上面說了那么多關(guān)于擴(kuò)展點(diǎn)的東西,那么具體什么是擴(kuò)展點(diǎn)呢,下面看下 Dubbo 擴(kuò)展點(diǎn)一個(gè)簡單例子。以擴(kuò)展點(diǎn) Protocol 為例:

@SPI("dubbo")
public interface Protocol {
...
}

擴(kuò)展點(diǎn)接口的類上面都含有@SPI注解,這里注解里面的"dubbo"說明Protocol擴(kuò)展接口SPI的默認(rèn)實(shí)現(xiàn)是DubboProtocol。

如果我們想自己寫一個(gè) Protocol 擴(kuò)展接口的實(shí)現(xiàn)類,那么我們需要在實(shí)現(xiàn)類所在的 Jar 包內(nèi)的 META-INF/dubbo/ 目錄下創(chuàng)建一個(gè)名字為 org.apache.dubbo.rpc.Protocol 的文本文件,然后配置它的內(nèi)容為:

myprotocol=com.alibaba.user.MyProtocol

假設(shè)該實(shí)現(xiàn)類 MyProtocol 的內(nèi)容如下:

package com.alibaba.user;
public class MyProtocol implemenets Protocol {
// ...
}

那么如何使用我們自定義的擴(kuò)展實(shí)現(xiàn)呢?Dubbo 配置模塊中,擴(kuò)展點(diǎn)均有對(duì)應(yīng)配置屬性或標(biāo)簽,如下代碼通過配置標(biāo)簽方式指定使用哪個(gè)擴(kuò)展實(shí)現(xiàn):

<dubbo:protocol name="myprotocol" />

注意這里的 name 必須與 jar 包內(nèi) META-INF/dubbo/ 目錄下 org.apache.dubbo.rpc.Protocol 文件中的等號(hào)左側(cè)的key的名字一致。

3.2 Dubbo內(nèi)核原理

在Dubbo中框架的可擴(kuò)展性是靠適配器原理結(jié)合增強(qiáng)SPI機(jī)制實(shí)現(xiàn)的,本書中首先會(huì)講解Dubbo的適配器原理,什么是適配器模式?比如dubbo提供的擴(kuò)展接口Protocol,Protocol的定義如下:

@SPI("dubbo")
public interface Protocol {
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    ....
}

Dubbo則會(huì)使用本書介紹的動(dòng)態(tài)編譯技術(shù)為接口Protocol生成一個(gè)適配器類Protocol$Adaptive的對(duì)象實(shí)例,Dubbo框架中需要使用Protocol的實(shí)例的時(shí)候?qū)嶋H就是使用的Protocol$Adaptive的對(duì)象實(shí)例來獲取具體SPI實(shí)現(xiàn)類,其代碼如下:

package org.apache.dubbo.rpc;
...
public class Protocol$Adaptive implements Protocol {   
 ...
public Exporter export(Invoker invoker) throws RpcException {
    String string;
    ...
    //(1)
    URL uRL = invoker.getUrl();
    String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
    if (string == null) {
        throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL.toString()).append(") use keys([protocol])").toString());
    }
    //(2)
    Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
    //(3)
    return protocol.export(invoker);
}
    

在dubbo框架中protocol的一個(gè)定義為: private static final Protocol protocol =ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();當(dāng)調(diào)用protocol.export(wrapperInvoker)時(shí)候,實(shí)際是調(diào)用的Protocol$Adaptive的對(duì)象實(shí)例的export方法,然后后者根據(jù)wrapperInvoker中的url里面的協(xié)議類型參數(shù)執(zhí)行代碼(2)使用Dubbo增強(qiáng)SPI方法getExtension獲取對(duì)應(yīng)的SPI實(shí)現(xiàn)類,然后調(diào)用代碼(3)執(zhí)行具體SPI實(shí)現(xiàn)類的export方法。

然后本書會(huì)講解Dubbo增強(qiáng)的SPI機(jī)制,本書中首先會(huì)借助 java.sql.Driver擴(kuò)展接口講解標(biāo)準(zhǔn)JDK中的SPI實(shí)現(xiàn)原理以及缺陷,然后講解dubbo的增強(qiáng)SPI如何對(duì)其進(jìn)行改進(jìn),如何實(shí)現(xiàn)的擴(kuò)展接口之間自動(dòng)IOC和擴(kuò)展接口的功能增強(qiáng)AOP功能。

然后會(huì)講解Dubbo使用JavaAssist減少反射調(diào)用開銷:Dubbo會(huì)給每個(gè)服務(wù)提供者的實(shí)現(xiàn)類生產(chǎn)一個(gè)Wrapper類,這個(gè)wrapper類里面最終調(diào)用服務(wù)提供者的接口實(shí)現(xiàn)類,wrapper類的存在是為了減少反射的調(diào)用。當(dāng)服務(wù)提供方接受到消費(fèi)方發(fā)來的請(qǐng)求后需要根據(jù)消費(fèi)者傳遞過來的方法名和參數(shù)反射調(diào)用服務(wù)提供者的實(shí)現(xiàn)類,而反射本身是有性能開銷的,所以dubbo把每個(gè)服務(wù)提供者的實(shí)現(xiàn)類通過JavaAssist包裝為一個(gè)Wrapper類,那么Wrapper類為何能減少反射調(diào)用那?觀看本書就可以找到答案

3.3 Dubbo功能實(shí)現(xiàn)原理

講解完畢支撐Dubbo框架的內(nèi)核原理后,本書會(huì)先從整體剖析Dubbo服務(wù)提供端如何發(fā)布服務(wù)的,這包含發(fā)布本地服務(wù)和發(fā)布遠(yuǎn)程服務(wù)的流程, Dubbo服務(wù)導(dǎo)出分 本地導(dǎo)出與遠(yuǎn)程導(dǎo)出,本地導(dǎo)出使用了 injvm 協(xié)議,是一個(gè)偽協(xié)議,它不開啟端口,不發(fā)起遠(yuǎn)程調(diào)用,只在 JVM 內(nèi)直接關(guān)聯(lián),但執(zhí)行 Dubbo 的 Filter 鏈;默認(rèn)下 Dubbo 同時(shí)支持本地導(dǎo)出與遠(yuǎn)程導(dǎo)出協(xié)議,可以通過ServiceConfig的setScope方式設(shè)置,其中配置為none表示不導(dǎo)出服務(wù),為remote表示只導(dǎo)出遠(yuǎn)程服務(wù),為local表示只導(dǎo)出本地服務(wù)。

你會(huì)知道Dubbo如何實(shí)現(xiàn)的服務(wù)延遲發(fā)布,如何把服務(wù)實(shí)現(xiàn)類轉(zhuǎn)換為 Wrapper 類,以便減少反射的調(diào)用,什么時(shí)候構(gòu)建的dubbo的Filter鏈,都有哪些Wrapper類對(duì)擴(kuò)展接口的實(shí)現(xiàn)類進(jìn)行了功能增強(qiáng)?如何啟動(dòng)的NettyServer對(duì)服務(wù)進(jìn)行監(jiān)聽,同一個(gè)機(jī)器上的多個(gè)服務(wù)提供接口是啟動(dòng)多個(gè)NettyServer還是一個(gè)?如何做到的?如何注冊(cè)服務(wù)到服務(wù)注冊(cè)中心的?服務(wù)注冊(cè)到zookeeper后,其存儲(chǔ)結(jié)構(gòu)是怎么樣的?

然后本書會(huì)講解當(dāng)服務(wù)提供方接受到請(qǐng)求后,如何進(jìn)行處理的,這包含F(xiàn)ilter鏈對(duì)請(qǐng)求的處理,以及如何找到對(duì)應(yīng)的被wrapper類包裝后的服務(wù)實(shí)現(xiàn)類,并對(duì)請(qǐng)求進(jìn)行處理,如何實(shí)現(xiàn)的Dubbo的服務(wù)提供端異步執(zhí)行。

然后會(huì)講解Dubbo服務(wù)消費(fèi)端的啟動(dòng)流程,這個(gè)過程,你會(huì)知道如何基于Proxy SPI擴(kuò)展實(shí)現(xiàn)對(duì)服務(wù)接口進(jìn)行代理。與服務(wù)提供端一樣,消費(fèi)端可以設(shè)置是否需要本地服務(wù)引用,你會(huì)知道在消費(fèi)端如果沒有指定scope類型,在啟動(dòng)時(shí)候會(huì)檢查當(dāng)前jvm內(nèi)是否有導(dǎo)出的服務(wù),如果有則自動(dòng)開啟本地引用(也就是協(xié)議類型修改為injvm),則具體調(diào)用時(shí)候會(huì)使用本地暴露的服務(wù)來提供服務(wù),而不發(fā)起遠(yuǎn)程調(diào)用。

當(dāng)具體發(fā)起遠(yuǎn)程調(diào)用時(shí)候,你會(huì)知道如何動(dòng)態(tài)從服務(wù)注冊(cè)中心動(dòng)態(tài)訂閱服務(wù)信息的,比如訂閱服務(wù)提供者地址列表,服務(wù)降級(jí)信息,服務(wù)路由信息,以及Directory目錄與Router路由服務(wù),以及什么時(shí)候構(gòu)建的路由規(guī)則鏈。

如何啟動(dòng)NettyClient具體發(fā)起遠(yuǎn)程調(diào)用的。然后你會(huì)知道同一個(gè)服務(wù)提供者機(jī)器可以提供多個(gè)服務(wù),那么消費(fèi)者機(jī)器需要與同一個(gè)服務(wù)提供者機(jī)器提供的多個(gè)共享連接還是與每個(gè)服務(wù)都建立一個(gè)?消費(fèi)端是啟動(dòng)時(shí)候就與服務(wù)提供者機(jī)器建立好連接?

然后會(huì)講解具體如何發(fā)起一次遠(yuǎn)程調(diào)用,這個(gè)過程你會(huì)知道當(dāng)發(fā)起一次rpc調(diào)用時(shí)候會(huì)先經(jīng)過MockInvoker進(jìn)行處理,其會(huì)看是否設(shè)置了 force:return 降級(jí)策略,如果設(shè)置了則直接返回 mock 值,并不發(fā)起遠(yuǎn)程調(diào)用;否者發(fā)起遠(yuǎn)程調(diào)用,如果遠(yuǎn)程調(diào)用結(jié)果 OK,則直接返回遠(yuǎn)程調(diào)用返回的結(jié)果;如果遠(yuǎn)程調(diào)用失敗了,則看當(dāng)前是否設(shè)置了 fail:return 的降級(jí)策略,如果設(shè)置了,則直接返回 mock 值,否者返回調(diào)用遠(yuǎn)程服務(wù)失敗的具體原因。

如果沒有設(shè)置服務(wù)降級(jí)策略或者mock服務(wù),則會(huì)基于SPI機(jī)制選擇具體的集群容錯(cuò)策略(本文會(huì)詳細(xì)講解常見的Failover、Failfast、Failsafe、Forking、Broadcast這幾種集群容錯(cuò)實(shí)現(xiàn)原理,以及講解如何自己基于SPI實(shí)現(xiàn)自己的容錯(cuò)策略),具體集群容錯(cuò)策略內(nèi)有會(huì)根據(jù)SPI機(jī)制選擇設(shè)置的服務(wù)負(fù)載均衡策略(本文會(huì)詳細(xì)介紹常見的Random、RoundRobin、LeastActive、ConsistentHash),具體負(fù)載均衡策略內(nèi)會(huì)基于SPI選擇設(shè)置的服務(wù)目錄實(shí)現(xiàn),其內(nèi)部維護(hù)了所有服務(wù)提供者的服務(wù)提供者列表與路由規(guī)則,負(fù)載均衡策略則會(huì)從符合路由規(guī)則的地址列表里面選擇一個(gè)invoker返回,然后最終有該invoker執(zhí)行。如果執(zhí)行失敗了,則根據(jù)具體集群容錯(cuò)策略重新選擇一個(gè)invoker進(jìn)行執(zhí)行....

然后本書會(huì)講解Dubbo線程模型與線程池策略,Dubbo 默認(rèn)的底層網(wǎng)絡(luò)通訊使用的是 Netty ,服務(wù)提供方 NettyServer 使用兩級(jí)線程池,其中 EventLoopGroup(boss) 主要用來接受客戶端的鏈接請(qǐng)求,并把接受的請(qǐng)求分發(fā)給 EventLoopGroup(worker) 來處理,boss 和 worker 線程組我們稱之為 IO 線程。

如果服務(wù)提供方的邏輯能迅速完成,并且不會(huì)發(fā)起新的 IO 請(qǐng)求,那么直接在 IO 線程上處理會(huì)更快,因?yàn)檫@減少了線程池調(diào)度與上下文切換開銷。但如果處理邏輯較慢,或者需要發(fā)起新的 IO 請(qǐng)求,比如需要查詢數(shù)據(jù)庫,則 IO 線程必須派發(fā)請(qǐng)求到新的線程池進(jìn)行處理,否則 IO 線程會(huì)被阻塞,將導(dǎo)致不能接收其它請(qǐng)求。

Dubbo中在服務(wù)提供端與消費(fèi)端的IO線程對(duì)請(qǐng)求處理時(shí)候默認(rèn)是把請(qǐng)求轉(zhuǎn)交給dubbo框架的內(nèi)部線程池來進(jìn)行處理的,以便可以及時(shí)釋放IO線程。

根據(jù)IO線程把什么類型的消息或者請(qǐng)求交給內(nèi)部線程池來處理,dubbo提供了不同的線程模型,本書主要講解Dubbo提供的線程模型AllDispatcher、DirectDispatcher、MessageOnlyDispatcher、ExecutionDispatcher、ConnectionOrderedDispatcher的實(shí)現(xiàn)原理,以及線程池策略FixedThreadPool、LimitedThreadPool、EagerThreadPool、CachedThreadPool的實(shí)現(xiàn)原理,以及如何基于SPI自定義自己的線程模型與線程池策略。

基礎(chǔ)篇我們講解到,基于Dubbo APi搭建Dubbo服務(wù)時(shí)候,服務(wù)消費(fèi)端引入了一個(gè) SDK 二方包,里面存放著服務(wù)提供端提供的所有接口類,泛化接口調(diào)用方式主要在服務(wù)消費(fèi)端沒有 API 接口類及模型類元(比如入?yún)⒑统鰠⒌?POJO 類)的情況下使用。其參數(shù)及返回值中沒有對(duì)應(yīng)的 POJO 類,所以所有 POJO 均轉(zhuǎn)換為 Map 表示。使用泛化調(diào)用時(shí)候服務(wù)消費(fèi)模塊不再需要引入 SDK 二方包,本書會(huì)詳細(xì)介紹Dubbo中nativejava,true, bean三種泛化調(diào)用的實(shí)現(xiàn)。

基礎(chǔ)篇我們講到Dubbo提供了隱式參數(shù)傳遞的功能,即服務(wù)調(diào)用方可以通過RpcContext.getContext().setAttachment()方法設(shè)置附加屬性鍵值對(duì),然后設(shè)置的值對(duì)可以在服務(wù)提供方服務(wù)方法內(nèi)獲?。槐緯覀儠?huì)詳細(xì)介紹如何在在消費(fèi)端設(shè)置參數(shù),并且如何通過網(wǎng)絡(luò)把參數(shù)傳遞到服務(wù)提供方,然后服務(wù)提供方如何進(jìn)行獲取。

正如Dubbo官網(wǎng)所說dubbo從2.7.0版本開始支持所有異步編程接口以CompletableFuture為基礎(chǔ),以便解決2.7.0之前版本異步調(diào)用的不便與功能缺失。

異步調(diào)用實(shí)現(xiàn)是基于 NIO 的非阻塞能力實(shí)現(xiàn)并行調(diào)用,服務(wù)消費(fèi)端不需要啟動(dòng)多線程即可完成并行調(diào)用多個(gè)遠(yuǎn)程服務(wù),相對(duì)多線程開銷較小,如下圖是Dubbo異步調(diào)用鏈路概要流程圖圖:


image.png

本書我們首先講解dubbo服務(wù)消費(fèi)端的異步調(diào)用,首先講解2.7.0版本前的異步調(diào)用實(shí)現(xiàn)原理,我們會(huì)知道future調(diào)用get()方法方式實(shí)現(xiàn)異步缺點(diǎn)是當(dāng)業(yè)務(wù)線程調(diào)用get()方法后業(yè)務(wù)線程會(huì)被阻塞,這不是我們想要的,所以dubbo2.7.0版本提供了在CompletableFuture對(duì)象上設(shè)置回調(diào)函數(shù)的方式,讓我們實(shí)現(xiàn)真正的異步調(diào)用。

在Provider端非異步執(zhí)行時(shí)候,其對(duì)調(diào)用方發(fā)來的請(qǐng)求的處理是在Dubbo內(nèi)部線程模型的線程池中的線程來執(zhí)行的,在dubbo中服務(wù)提供方提供的所有的服務(wù)接口都是使用這一個(gè)線程池來執(zhí)行的,所以當(dāng)一個(gè)服務(wù)執(zhí)行比較耗時(shí)時(shí)候,可能會(huì)占用線程池中很多線程,這可能就會(huì)導(dǎo)致其他服務(wù)的處理收到影響。

Provider端異步執(zhí)行則將服務(wù)的處理邏輯從Dubbo內(nèi)部線程池切換到業(yè)務(wù)自定義線程,避免Dubbo線程池中線程被過度占用,有助于避免不同服務(wù)間的互相影響。

但是需要注意provider端異步執(zhí)行對(duì)節(jié)省資源和提升RPC響應(yīng)性能是沒有效果的,這時(shí)是因?yàn)槿绻?wù)處理比較耗時(shí),雖然不是使用Dubbo框架內(nèi)部線程處理,但是還是需要業(yè)務(wù)自己的線程來處理,另外副作用還有會(huì)新增一次線程上下文切換(從dubbo內(nèi)部線程池線程切換到業(yè)務(wù)線程),模型如下圖11.2.0

image.png

本書首先會(huì)講解基于定義CompletableFuture簽名的接口實(shí)現(xiàn)異步執(zhí)行的實(shí)現(xiàn)原理,然后講解使用AsyncContext實(shí)現(xiàn)異步執(zhí)行原理,最后講解Dubbo的異步調(diào)用與執(zhí)行引入的新問題以及如何解決的,這包含引入異步調(diào)用時(shí)候等結(jié)果返回后Filter鏈得不到執(zhí)行的問題,以及異步執(zhí)行時(shí)候上下文參數(shù)傳遞問題。

前面章節(jié)我們介紹了服務(wù)消費(fèi)端一次服務(wù)調(diào)用流程與服務(wù)提供端一次服務(wù)處理流程,但是還是有一些東西是我們沒有提到的,比如服務(wù)消費(fèi)端如何把服務(wù)請(qǐng)求信息序列化為二進(jìn)制、服務(wù)提供方又是如何把消費(fèi)端發(fā)送的二進(jìn)制數(shù)據(jù)反序列化為可識(shí)別的POJO對(duì)象、比如Dubbo的應(yīng)用層協(xié)議是怎么樣的。本書我們就來一一來看dubbo是如何做這些的。

本書會(huì)首先講解Dubbo協(xié)議,在TCP協(xié)議棧中,每層協(xié)議都有自己的協(xié)議報(bào)文格式,比如TCP協(xié)議是網(wǎng)絡(luò)七層模型中的傳輸層,有TCP協(xié)議報(bào)文格式;在TCP上層是應(yīng)用層,應(yīng)用層協(xié)議常見的有http協(xié)議等,Dubbo協(xié)議作為建立在TCP協(xié)議之上的一種應(yīng)用層協(xié)議,自然也有自己的協(xié)議包格式,Dubbo協(xié)議也是參考TCP協(xié)議棧中的協(xié)議,協(xié)議內(nèi)容由header和body兩部分組成,本書會(huì)詳細(xì)介紹協(xié)議header中每個(gè)字段含義。然后講解服務(wù)消費(fèi)方編碼原理,包含當(dāng)服務(wù)消費(fèi)端發(fā)送請(qǐng)求時(shí)候,如何把請(qǐng)求內(nèi)容封裝為Dubbo協(xié)議幀的。然后講解服務(wù)提供方接受請(qǐng)求后如何對(duì)協(xié)議幀進(jìn)行解碼解決半包粘包問題的。

四、Dubbo-實(shí)踐篇

實(shí)踐篇我們來探討如何使用Arthas和一些demo來對(duì)研究Dubbo框架實(shí)現(xiàn)提供便捷,并且基于Netty與CompletableFuture模擬了RPC同步與純異步調(diào)用。

首先本書會(huì)介紹如何安裝Arthas,然后講解如何使用Arthas查看查看擴(kuò)展接口適配器類的源碼,查看服務(wù)提供端Wrapper類的源碼,如何查詢Dubbo啟動(dòng)后都有哪些Filter,然后通過Demo驗(yàn)證RoundRobin LoadBalance負(fù)載均衡原理,然后探討如果根據(jù)IP動(dòng)態(tài)路由調(diào)用Dubbo服務(wù)。

Dubbo的服務(wù)消費(fèi)端基于CompletableFuture實(shí)現(xiàn)了功能比較豐富的純異步調(diào)用,其實(shí)還不單單是CompletableFuture的功勞,歸根到底是Netty的NIO非阻塞功能提供的底層實(shí)現(xiàn),本文我們就來基于CompletableFuture與Netty來模擬下如何異步發(fā)起遠(yuǎn)程調(diào)用,以及如何使用CompletableFuture本身的功能,讓多個(gè)請(qǐng)求的異步結(jié)果進(jìn)行運(yùn)算,以便加深對(duì)dubbo異步調(diào)用實(shí)現(xiàn)原理的理解。

五、總結(jié)

如何你對(duì)上面內(nèi)容感興趣,想深入研究,但是無從入手,那么機(jī)會(huì)來了,專欄

內(nèi)容包含但是不限于上述內(nèi)容,大家可以掃描訂閱該專欄。

歡迎關(guān)注微信公眾號(hào):


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容