1. 程式人生 > >RPC入門總結(九)Dubbo框架實現細節

RPC入門總結(九)Dubbo框架實現細節

一、Dubbo的註冊中心

服務註冊中心是Dubbo中的重要組成部分,服務的提供者將服務釋出到註冊中心,服務的使用著到註冊中引用服務。Dubbo的註冊中心提供了多種實現,其實現是基於dubbo的SPI的擴充套件機制的,使用者可以直接實現自己的註冊中心

@SPI("dubbo")
public interface RegistryFactory {
   /**
    * 連線註冊中心.
    * 連線註冊中心需處理契約
    * 1. 當設定check=false時表示不檢查連線,否則在連線不上時丟擲異常。
    * 2. 支援URL上的username:password許可權認證。
    * 3. 支援backup=10.20.153.10備選註冊中心叢集地址。
    * 4. 支援file=registry.cache本地磁碟檔案快取。
    * 5. 支援timeout=1000請求超時設定。
    * 6. 支援session=60000會話超時或過期設定。
    * @param url 註冊中心地址,不允許為空
    * @return 註冊中心引用,總不返回空
    */
    @Adaptive({"protocol"})
    RegistrygetRegistry(URL url);
}
RegistryFactory用來建立註冊中心, 預設的註冊中心是dubbo協議,由於阿里的註冊中心並沒有開源,dubbo協議註冊中心只提供了一個簡單實現。 開源dubbo的註冊中心推薦使用zookeeper。這裡我們主要去分析基於dubbo和zookeeper協議的註冊中心實現及使用。服務介面定義
public interface RegistryService {
    void register(URL url);
    void unregister(URL url);
    void subscribe(URL url, NotifyListener listener);
    void unsubscribe(URL url, NotifyListener listener);
List<URL> lookup(URL url);
}
Register:註冊資料,比如:提供者地址,消費者地址,路由規則,覆蓋規則等資料。
註冊需處理契約
     1. 當URL設定了check=false時,註冊失敗後不報錯,在後臺定時重試,否則丟擲異常。
     2. 當URL設定了dynamic=false引數,則需持久儲存,否則,當註冊者出現斷電等情況異常退出時,需自動刪除。
     3. 當URL設定了category=routers時,表示分類儲存,預設類別為providers,可按分類部分通知資料。
     4. 當註冊中心重啟,網路抖動,不能丟失資料,包括斷線自動刪除資料。
     5. 允許URI相同但引數不同的URL並存,不能覆蓋。
Unregister
:取消註冊
Subscribe:訂閱符合條件的已註冊資料,當有註冊資料變更時自動推送
訂閱需處理契
     1. 當URL設定了check=false時,訂閱失敗後不報錯,在後臺定時重試。
     2. 當URL設定了category=routers,只通知指定分類的資料,多個分類用逗號分隔,並允許星號通配,表示訂閱所有分類資料。
     3. 允許以interface,group,version,classifier作為條件查詢,如:interface=com.alibaba.foo.BarService&version=1.0.0
     4. 並且查詢條件允許星號通配,訂閱所有介面的所有分組的所有版本,或:interface=*&group=*&version=*&classifier=*
     5. 當註冊中心重啟,網路抖動,需自動恢復訂閱請求
     6. 允許URI相同但引數不同的URL並存,不能覆蓋
     7. 必須阻塞訂閱過程,等第一次通知完後再返回。
Unsubscribe:取消訂閱
Lookup: 查詢符合條件的已註冊資料,與訂閱的推模式相對應,這裡為拉模式,只返回一次結果

二、Dubbo的SimpleRegistryService實現

基於dubbo協議開源只是給出了預設一個註冊中心實現SimpleRegistryService, 它只是一個簡單實現,不支援叢集,就是利用Map<String/*ip:port*/, Map<String/*service*/, URL>來儲存服務地址,分為以下流程;

1. 註冊中心啟動

SimpleRegistryService本身也是作為一個dubbo服務暴露。

<dubbo:protocolport="9090" />
<dubbo:service interface="com.alibaba.dubbo.registry.RegistryService"ref="registryService" registry="N/A" ondisconnect="disconnect"callbacks="1000">
     <dubbo:methodname="subscribe"><dubbo:argument index="1" callback="true"/></dubbo:method>
     <dubbo:methodname="unsubscribe"><dubbo:argument index="1" callback="false"/></dubbo:method>
</dubbo:service>
<bean id="registryService"class="com.alibaba.dubbo.registry.simple.SimpleRegistryService" />
上面是暴露註冊中心的dubbo服務配置,定義了註冊中心服務的埠號,釋出RegistryService服務。

 registry屬性是”N/A”代表不能獲取註冊中心,註冊中心服務的釋出也是一個普通的dubbo服務的釋出,如果沒有配置這個屬性它也會尋找註冊中心,去通過註冊中心釋出,因為自己本身就是註冊中心,直接對外發布服務,外部通過ip:port直接使用。
服務釋出定義了回撥介面, 這裡定義了subscribe的第二個入參類暴露的回撥服務供註冊中心回撥,用來當註冊的服務狀態變更時反向推送到客戶端。
Dubbo協議的註冊中心的暴露以及呼叫過程過程跟普通的dubbo服務的其實是一樣的,可能跟絕大多數服務的不同的是在SimpleRegistryService在被接收訂閱請求subscribe的時候,同時會refer引用呼叫方暴露的NotifyListener服務,當有註冊資料變更時自動推送

2. 生產者釋出服務

Dubbo協議向註冊中心釋出服務:當服務提供方,向dubbo協議的註冊中心釋出服務的時候,是如何獲取,建立註冊中心的,如何註冊以及訂閱服務的,下面我們來分析其流程。
看如下配置釋出服務:

<dubbo:registry protocol=”dubbo” address="127.0.0.1:9090" />
<beanid="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<dubbo:serviceinterface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
1. 指定了哪種的註冊中心,是基於dubbo協議的,指定了註冊中心的地址以及埠號
2. 釋出DemoService服務,服務的實現為DemoServiceImpl
 
每個<dubbo:service/>在spring內部都會生成一個ServiceBean例項,ServiceBean的例項化過程中呼叫export方法來暴露服務

1. 服務釋出的流程

1. 通過loadRegistries獲取註冊中心registryUrls

registry://127.0.0.1:9090/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.5.4-SNAPSHOT&owner=william&pid=7084&registry=dubbo×tamp=1415711791506
用統一資料模型URL表示:
  • protocol=registry表示一個註冊中心url
  • 註冊中心地址127.0.0.1:9090
  • 呼叫註冊中心的服務RegistryService
  • 註冊中心協議是registry=dubbo
2. 構建釋出服務的URL
dubbo://192.168.0.102:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.5.4-SNAPSHOT&generic=false&interface=com.alibaba.dubbo.demo.DemoService&loadbalance=roundrobin&methods=sayHello&owner=william&pid=7084&side=provider×tamp=1415712331601
  • 釋出協議protocol =dubbo
  • 服務提供者的地址為192.168.0.102:20880
  • 釋出的服務為com.alibaba.dubbo.demo.DemoService
3. 遍歷registryUrls向註冊中心註冊服務

給每個registryUrl新增屬性key為export,value為上面的釋出服務url得到如下registryUrl:

registry://127.0.0.1:9098/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.5.4-SNAPSHOT&export=dubbo%3A%2F%2F192.168.0.102%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.5.4-SNAPSHOT%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26loadbalance%3Droundrobin%26methods%3DsayHello%26owner%3Dwilliam%26pid%3D7084%26side%3Dprovider%26timestamp%3D1415712331601&owner=william&pid=7084&registry=dubbo×tamp=1415711791506
4. 由釋出的服務例項,服務介面以及registryUrl為引數,通過代理工廠proxyFactory獲取Invoker物件,Invoker物件是dubbo的核心模型,其他物件都向它靠攏或者轉換成它。
5. 通過Protocol物件暴露服務protocol.export(invoker)
通過DubboProtocol暴露服務的監聽(不是此節內容)
通過RegistryProtocol將服務地址釋出到註冊中心,並訂閱此服務

2. RegistryProtocol.export(Invoker)暴露服務

1. 調DubboProtocol暴露服務的監聽
2. 獲取註冊中心getRegistry(Invoker)
URL轉換, 由Invoker獲取的url是registryURL它的協議屬性用來選擇何種的Protocol例項如RegistryProtocol, DubboProtocol或者RedisProtocol等等。 這裡要通過URL去選擇何種註冊中心,所以根據registry=dubbo屬性,重新設定url的協議屬性得registryUrl

dubbo://127.0.0.1:9098/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.5.4-SNAPSHOT&export=dubbo%3A%2F%2F192.168.0.102%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.5.4-SNAPSHOT%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26loadbalance%3Droundrobin%26methods%3DsayHello%26owner%3Dwilliam%26pid%3D5040%26side%3Dprovider%26timestamp%3D1415715706560&owner=william&pid=5040×tamp=1415715706529
RegistryFactory.getRegistry(url) 通過工廠類建立註冊中心,RegistryFactory通過dubbo的spi機制獲取對應的工廠類, 這裡的是基於dubbo協議的註冊中心,所以是DubboRegistryFactory
3. 獲取釋出url ,就是registryUrl的export引數的值
registryProviderUrl=dubbo://10.33.37.7:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.5.4-SNAPSHOT&generic=false&interface=com.alibaba.dubbo.demo.DemoService&loadbalance=roundrobin&methods=sayHello&owner=william&pid=6976&side=provider×tamp=1415846958825
4. DubboRegistry.register(registryProviderUrl)

5. 構建訂閱服務overrideProviderUrl,我們是釋出服務

6. 構建OverrideListener它實現與NotifyLisener,當註冊中心的訂閱的url發生變化時回撥重新export
7. registry.subscribe(overrideProviderUrl, OverrideListener), 註冊器向註冊中心訂閱overrideProviderUrl,同時將Override Listener暴露為回撥服務,當註冊中心的overrideProviderUrl資料發生變化時回撥,
註冊器DubboRegistry的registry,subscribe, unRegistry, unSubscribe都類似, 是一個dubbo的遠端服務呼叫

3. 消費者引用服務

消費者使用如下的配置指明服務:

<dubbo:registry protocol=”dubbo” address="127.0.0.1:9098" />
<dubbo:reference id="demoService"interface="com.alibaba.d ubbo.demo.DemoService"/>
1. 指定了哪種的註冊中心,是基於dubbo協議的,指定了註冊中心的地址以及埠號
2. 引用遠端DemoService服務
每個<dubbo:reference/>標籤spring載入的時候都會生成一個ReferenceBean。


如上圖ReferenceBean實現了spring的FactoryBean介面, 實現了此介面的Bean通過spring的BeanFactory.getBean(“beanName”)獲取的物件不是配置的bean本身而是通過FactoryBean.getObject()方法返回的物件,此介面在spring內部被廣泛使用,用來獲取代理物件等等。這裡getObject方法用來生成對遠端服務呼叫的代理

1. loadRegistries()獲取配置的註冊中心的registryUrls
2. 遍歷registryUrls集合,給registryUrl加上refer key就是要引用的遠端服務

[registry://127.0.0.1:9098/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=2484&refer=application%3Ddemo-consumer%26dubbo%3D2.0.0%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D2484%26side%3Dconsumer%26timestamp%3D1415879965901&registry=dubbo×tamp=1415879990670]
3. 遍歷registryUrls集合,使用Protocol.refer(interface,regist ryUrl)的到可執行物件invoker
4. 如果註冊中心有多個的話, 通過叢集策略Cluser.join()將多個invoke r偽裝成一個可執行invoker, 這裡預設使用available策略
5. 利用代理工廠生成代理物件proxyFactory.getProxy(invoker)

三、Dubbo的Zookeeper協議註冊中心

開源dubbo推薦的業界成熟的zookeeper做為註冊中心, zookeeper是hadoop的一個子專案是分散式系統的可靠協調者,他提供了配置維護,名字服務,分散式同步等服務。

Dubbo服務提供者配置

<dubbo:registry protocol=”zookeeper” address="127.0.0. 1:2181" />
<beanid="demoService" class="com.alibaba.dubbo.demo.provi der.DemoServiceImpl"/>
<dubbo:serviceinterface="com.alibaba.dubbo.demo.DemoServi ce" ref="demoService"/>
Dubbo服務消費者配置
<dubbo:registry protocol=”zookeeper” address="127.0.0. 1:2181" />
<dubbo:referenceid="demoService"interface="com.alibaba.dubbo.demo.DemoService"/>
客戶端獲取註冊器

服務的提供者和消費者在RegistryProtocol利用註冊中心暴露(export)和引用(refer)服務的時候會根據配置利用Dubbo的SPI機制獲取具體註冊中心註冊器

Registry registry = registryFactory.getRegistry(url);
這裡的RegistryFactory是ZookeeperRegistryFactory看如下工廠程式碼
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
    public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }
}
這裡建立zookeepr註冊器ZookeeperRegistry。ZookeeperTransporter是操作zookeepr的客戶端的工廠類,用來建立zookeeper客戶端,這裡客戶端並不是zookeeper原始碼的自帶的,而是採用第三方工具包,主要來簡化對zookeeper的操作,例如用zookeeper做註冊中心需要對zookeeper節點新增watcher做反向推送,但是每次回撥後節點的watcher都會被刪除,這些客戶會自動維護了這些watcher,在自動新增到節點上去。
介面定義:

@SPI("zkclient")
public interface ZookeeperTransporter {
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    ZookeeperClient connect(URL url);
}
預設採用zkClient, dubbo原始碼整合兩種zookeeper客戶端,除了zkClient還有一個是curator

ZookeeperRegistry註冊器的實現

1.構造器利用客戶端建立了對zookeeper的連線,並且添加了自動回覆連線的監聽器

zkClient = zookeeperTransporter.connect(url);
    zkClient.addStateListener(new StateListener() {
            public void stateChanged(int state) {
                if (state ==RECONNECTED)
                   recover();
          }
});
2.註冊url就是利用客戶端在伺服器端建立url的節點,預設為臨時節點,客戶端與服務端斷開,節點自動刪除
zkClient.create(toUrlPath(url),url.getParameter(Constants.DYNAMIC_KEY,true));      
3.取消註冊的url,就是利用zookeeper客戶端刪除url節點
zkClient.delete(toUrlPath(url));

4. 訂閱url, 功能是服務消費端訂閱服務提供方在zookeeper上註冊地址,

這個功能流程跟DubboRegister不一樣, DubboRegister是通過Dubbo註冊中心實現SimpleResgiter在註冊中心端,對url變換、過濾篩選然後將獲取的provierUrl(提供者ulr)利用服務消費者暴露的服務回撥在refer。

由於這裡註冊中心採用的是zookeeper,zookeeper不可能具有dubbo的業務邏輯,這裡對訂閱的邏輯處理都在消費服務端訂閱的時候處理
1) 對傳入url的serviceInterface是*,代表訂閱url目錄下所有節點即所有服務,這個註冊中心需要訂閱所有
2) 如果指定了訂閱介面通過toCategoriesPath(url)轉換需要訂閱的url

如傳入

url consumer://10.33.37.8/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=providers,configurators,routers&dubbo=2.5.4-SNAPSHOT&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=4088&side=consumer×tamp=1417405597808

轉換成:

urls/dubbo/com.alibaba.dubbo.demo.DemoService/providers,/dubbo/com.alibaba.dubbo.demo.DemoService/configurators, /dubbo/com.alibaba.dubbo.demo.DemoService/routers
3) 設配傳入的回撥介面NotifyListener,轉換成dubbo對zookeeper操作的ChildListener

4) 以/dubbo/com.alibaba.dubbo.demo.DemoService/providers為例建立節點:

zkClient.create(path, false);
但是一般情況下如果服務提供者已經提供服務,那麼這個目錄節點應該已經存在,Dubbo在Client層遮蔽掉了建立異常。 5) 以/dubbo/com.alibaba.dubbo.demo.DemoService/providers為例給節點新增監聽器,返回所有子目錄
List<String> children = zkClient.addChildListener(path, zkListener);
        if (children !=null) {urls.addAll(toUrlsWithEmpty(url, path,hildren));}
        toUrlsWtihEmpty用來配置是不是需要訂閱的url,是加入集合
6) 主動根據得到服務提供者urls回撥NotifyListener,引用服務提供者生成invoker可執行物件
5. 取消訂閱url, 只是去掉url上的註冊的監聽器

四、Dubbo的叢集和路由

1.Dubbo叢集

Dubbo作為一個分散式的服務治理框架,提供了叢集部署,路由,軟負載均衡及容錯機制,Dubbo Cluster將Directory中的多個Invoker偽裝成一個Invoker, 對上層透明,包含叢集的容錯機制。


Cluster介面定義

@SPI(FailoverCluster.NAME)
public interface Cluster {
    @Adaptive
    <T> Invoker<T>join(Directory<T> directory) throws RpcException;
}
Cluster可以看做是工廠類, 將目錄directory下的invoker合併成一個統一的Invoker,根據不同叢集策略的Cluster建立不同的Invoker。

我們來看下預設的失敗轉移,當出現失敗重試其他服務的策略,這個Cluster實現很簡單就是建立FailoverCluseterInvoker物件:

public class FailoverCluster implements Cluster {
    public final static String NAME ="failover";
    public<T> Invoker<T> join(Directory<T> directory)throws RpcException{
        return new FailoverClusterInvoker<T>(directory);
    }
}

1)AvailableCluster: 獲取可用的呼叫。遍歷所有Invokers判斷Invoker.isAvalible,只要一個有為true直接呼叫返回,不管成不成功
2)BroadcastCluster: 廣播呼叫。遍歷所有Invokers, 逐個呼叫每個呼叫catch住異常不影響其他invoker呼叫
3)FailbackCluster: 失敗自動恢復, 對於invoker呼叫失敗, 後臺記錄失敗請求,任務定時重發, 通常用於通知
4)FailfastCluster: 快速失敗,只發起一次呼叫,失敗立即報錯,通常用於非冪等性操作
5)FailoverCluster: 失敗轉移,當出現失敗,重試其它伺服器,通常用於讀操作,但重試會帶來更長延遲
  • (1)   目錄服務directory.list(invocation) 列出方法的所有可呼叫服務
  • (2)   根據LoadBalance負載策略選擇一個Invoker
  • (3)   執行invoker.invoke(invocation)呼叫
  • (4)   呼叫成功返回
  •             呼叫失敗小於重試次數,重新執行從3)步驟開始執行
  •             呼叫次數大於等於重試次數丟擲呼叫失敗異常
6)FailsafeCluster: 失敗安全,出現異常時,直接忽略,通常用於寫入審計日誌等操作。
7)ForkingCluster: 並行呼叫,只要一個成功即返回,通常用於實時性要求較高的操作,但需要浪費更多服務資源。
8)MergeableCluster: 分組聚合, 按組合並返回結果,比如選單服務,介面一樣,但有多種實現,用group區分,現在消費方需從每種group中呼叫一次返回結果,合併結果返回,這樣就可以實現聚合選單項

2. 叢集目錄服務Directory

叢集目錄服務Directory, 代表多個Invoker, 可以看成List<Invoker>,它的值可能是動態變化的,比如註冊中心推送變更。叢集選擇呼叫服務時通過目錄服務找到所有服務。

Directory的介面定義

public interfaceDirectory<T> extends Node {
    //服務型別
    Class<T>getInterface();
    //列出所有服務的可執行物件
    List<Invoker<T>>list(Invocation invocation) throws RpcException;
}
Directory有兩個具體實現:
  1. StaticDirectory: 靜態目錄服務, 它的所有Invoker通過建構函式傳入, 服務消費方引用服務的時候, 服務對多註冊中心的引用,將Invokers集合直接傳入 StaticDirectory構造器,再由Cluster偽裝成一個Invoker,StaticDirectory的list方法直接返回所有invoker集合。
  2. RegistryDirectory: 註冊目錄服務, 它的Invoker集合是從註冊中心獲取的, 它實現了NotifyListener介面實現了回撥介面notify(List<Url>)。比如消費方要呼叫某遠端服務,會向註冊中心訂閱這個服務的所有服務提供方,訂閱時和服務提供方資料有變動時回撥消費方的NotifyListener服務的notify方法NotifyListener.notify(List<Url>) 回撥介面傳入所有服務的提供方的url地址然後將urls轉化為invokers, 也就是refer應用遠端服務

3. Dubbo叢集路由

Router服務路由, 根據路由規則從多個Invoker中選出一個子集AbstractDirectory是所有目錄服務實現的上層抽象, 它在list列舉出所有invokers後,會在通過Router服務進行路由過濾。Router介面定義:

public interface Router extendsComparable<Router> {
    URL getUrl();
<T> List<Invoker<T>> route(List<Invoker<T>>invokers, URL url, Invocation invocation)throws RpcException;
}


ConditionRouter: 條件路由
我們這裡簡單分析下程式碼實現具體功能參考官方文件
條件表示式以 => 分割為whenRulethenRule
 
ConditionRouter建立,構造器初始

1)從url根據RULE_KEY獲取路由條件、路由內容
2)rule.indexOf("=>") 分割路由內容
3)分別呼叫parseRule(rule) 解析路由為whenRulethenRules
 
ConditionRouter執行route方法

1)如果url不滿足when條件即過濾條件, 不過濾返回所有invokers
2)遍歷所有invokers判斷是否滿足then條件, 將滿足條件的加入集合result
3)Result不為空,有滿足條件的invokers返回
4)Result為空, 沒有滿足條件的invokers, 判斷引數FORCE_KEY是否強制過濾,如果強制過濾則返回空, 否則返回所有即不過濾
 
ScriptRouter: 指令碼路由
通過url的RULE_KEY引數獲取指令碼內容,然後通過java的指令碼引擎執行指令碼程式碼。
 
ScriptRouter建立,構造器初始化
1)從url獲取指令碼型別javascript, groovy等等
2)從url根據RULE_KEY獲取路由規則內容
3)根據指令碼型別獲取java支援的指令碼執行引擎
 
ScriptRouter執行route方法
1)執行引擎建立引數繫結
2)繫結執行的引數
3)執行引擎編譯路由規則得到執行函式CompiledScript
4)CompiledScript.eval(binds) 根據引數執行路由規則
 
Dubbo提供了ConditionRouterFactory, ScriptRouterFactory來建立對應的路由,路由的規則從url的RULE_KEY引數來獲取,路由規則可以通過監控中心或者治理中心寫入註冊中心
RegistryFactory registryFactory =ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry =registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181");
registry.register(URL.valueOf("condition://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule="+ URL.encode("http://10.20.160.198/wiki/display/dubbo/host = 10.20.153.10=> host = 10.20.153.11") + "));
 
Dubbo也支援通過FileRouterFactory從檔案讀取路由規則,將讀取的規則設定到url的RULE_KEY引數上, 檔案的字尾代表了路由的型別,選擇具體的路由工廠 ConditionRouterFactory,ScriptRouterFactory來建立路由規則。

五、Dubbo的負載均衡

LoadBalance負載均衡, 負責從多個 Invokers中選出具體的一個Invoker用於本次呼叫,呼叫過程中包含了負載均衡的演算法,呼叫失敗後需要重新選擇。
LoadBalance介面定義

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance{
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers,URL url, Invocation invocation)throws RpcException;
}
類註解@SPI說明可以基於Dubbo的擴充套件機制進行自定義的負責均衡演算法實現,預設是隨機演算法
方法註解@Adaptive說明能夠生成適配方法
Select方法設配類通過url的引數選擇具體的演算法, 在從invokers集合中根據具體的演算法選擇一個invoker

1. RandomLoadBalance: 隨機訪問策略,按權重設定隨機概率,是預設策略
1)獲取所有invokers的個數
2)遍歷所有Invokers, 獲取計算每個invokers的權重,並把權重累計加起來,每相鄰的兩個invoker比較他們的權重是否一樣,有一個不一樣說明權重不均等
3)總權重大於零且權重不均等的情況下
按總權重獲取隨機數offset = random.netx(totalWeight);
遍歷invokers確定隨機數offset落在哪個片段(invoker上)

4)權重相同或者總權重為0, 根據invokers個數均等選擇

invokers.get(random.nextInt(length))

2. RoundRobinLoadBalance:輪詢,按公約後的權重設定輪詢比率
1)獲取輪詢key  服務名+方法名
獲取可供呼叫的invokers個數length
設定最大權重的預設值maxWeight=0
設定最小權重的預設值minWeight=Integer.MAX_VALUE
2)遍歷所有Inokers,比較出得出maxWeight和minWeight
3)如果權重是不一樣的
根據key獲取自增序列,自增序列加一與最大權重取模預設得到currentWeigth
遍歷所有invokers篩選出大於currentWeight的invokers
設定可供呼叫的invokers的個數length
4)自增序列加一併與length取模,從invokers獲取invoker
  
3. LeastActiveLoadBalance: 最少活躍呼叫數, 相同的活躍的隨機選擇,
活躍數是指呼叫前後的計數差, 使慢的提供者收到更少的請求,因為越慢的提供者前後的計數差越大。
活躍計數的功能消費者是在ActiveLimitFilter中設定的

1)獲取可呼叫invoker的總個數
初始化最小活躍數,相同最小活躍的個數
相同最小活躍數的下標陣列
等等
2)遍歷所有invokers, 獲取每個invoker的獲取數active和權重
找出最小權重的invoker
如果有相同最小權重的inovkers, 將下標記錄到陣列leastIndexs[]陣列中
累計所有的權重到totalWeight變數
3)如果invokers的權重不相等且totalWeight大於0
按總權重隨機offsetWeight = random.nextInt(totalWeight)
計算隨機值在哪個片段上並返回invoker

4)如果invokers的權重相等或者totalWeight等於0,均等隨機

5. ConsistentHashLoadBalance:一致性hash, 相同引數的請求總是發到同一個提供者,當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。