dubbo源碼分析15 -- 集羣容錯之Route

在集中式環境中服務的機器臺只有一臺,這樣對於服務不僅存在服務單點故障問題而且還存在流量問題。爲了解決這個問題,就引入的分佈式與集羣概念。

分佈式:一個業務分拆多個子業務,部署在不同的服務器上
集羣:同一個業務,部署在多個服務器上

1、 dubbo 服務治理

當請求來臨時,如何從多個服務器中,選擇一個有效、合適的服務器,這個集羣所需要面對一問題。所以在集羣裏面就引申出負載均衡(LoadBalance),高可用(HA),路由(Route)等概念。我們來看一下 dubbo 在進行服務調用的時候是如何處理的。

在這裏插入圖片描述

這張集羣容錯包含以下幾個角色:

  • Invoker:對 Provider (服務提供者) 的一個可調用 Service 接口的抽象,Invoker 封裝了 Provider 地址及 Service 接口信息。
  • ClusterDirectory 中的多個 Invoker 僞裝成一個 Invoker,對上層透明,僞裝過程包含了容錯邏輯,調用失敗後,重試另一個
  • Directory:代表多個 Invoker,可以把它看成 List<Invoker> ,但與 List 不同的是,它的值可能是動態變化的,比如註冊中心推送變更
  • Router : 負責從多個Invoker 中按路由規則選出子集,比如讀寫分離,應用隔離等
  • LoadBalanceLoadBalance 負責從多個 Invoker 中選出具體的一個用於本次調用,選的過程包含了負載均衡算法,調用失敗後,需要重選.

2、 路由服務

下面我們來分析一下 Route, 也就是路由服務。我們可以來看一下 維基百科, 對於路由服務的描述。

路由是在一個網絡中,或在多個網絡之間或跨多個網絡中選擇一條路徑的過程。路由是爲許多類型的網絡執行的,包括電路交換網絡,如公共交換電話網絡(PSTN)和計算機網絡,如因特網。

在分組交換網絡中,路由是通過特定的包轉發機制將網絡數據包從其源引導到目的地的高級決策。包轉發是從一個網絡接口到另一個網絡接口的邏輯處理網絡數據包的傳輸。中間節點通常是網絡硬件設備,如路由器、橋、網關、防火牆或交換機。通用計算機也轉發數據包並執行路由,儘管它們沒有專門針對任務的優化硬件。路由過程通常在路由表的基礎上進行轉發,它維護了到各種網絡目的地的路由記錄。因此,在路由器的內存中構建路由表對於有效的路由是非常重要的。大多數路由算法一次只使用一個網絡路徑。多路徑路由技術支持使用多種可選路徑。

在狹義的術語中,路由通常與橋接的關係形成對比,即網絡地址是結構化的,並且類似的地址意味着在網絡中接近。結構化的地址允許單個路由表條目表示到一組設備的路由。在大型網絡中,結構化尋址(狹義的路由)優於非結構化尋址(橋接)。路由已經成爲在互聯網上尋址的主要形式。

3、Route

在 dubbo 中路由規則決定一次服務調用的目標服務器,分爲條件路由規則和腳本路由規則,並且支持可擴展(SPI)。

下面就是 dubbo 裏面 Route 路由接口的定義:

public interface Router extends Comparable<Router> {

    URL getUrl();

    <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}

調用 route 方法,傳入從目錄服務獲取到的 Invoke 列表,通過 URL 或者 Invocation 裏面配置的條件篩選出滿足條件的 Invoke 列表。

下面是 dubbo 路由服務的類圖:

在這裏插入圖片描述

dubbo 默認會在 AbstractDirectory#setRouters 自動添加 MockInvokersSelector 路由規則。

3.1 MockInvokersSelector

MockInvokersSelector:其實就是用於路由 Mock 服務與非 Mock 服務。

    public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
                                      URL url, final Invocation invocation) throws RpcException {
        if (invocation.getAttachments() == null) {
            return getNormalInvokers(invokers);
        } else {
            String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
            if (value == null)
                return getNormalInvokers(invokers);
            else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
                return getMockedInvokers(invokers);
            }
        }
        return invokers;
    }

上面的代碼邏輯其實就是:

  • 如果 Invocation 的擴展參數不爲空 並且 Invocation 的擴展參數裏面包含 invocation.need.mock 參數並且值爲 true 就獲取 Invoke 列表裏面 protocolmock 的 Invoke 列表。
  • 否則獲取Invoke 列表裏面 protocol 爲非 mock 的 Invoke 列表。

3.2 ConditionRouter

ConditionRouter:基於條件表達式的路由規則,它的條件規則如下:

  • => 之前的爲消費者匹配條件,所有參數和消費者的 URL 進行對比,當消費者滿足匹配條件時,對該消費者執行後面的過濾規則。
  • => 之後爲提供者地址列表的過濾條件,所有參數和提供者的 URL 進行對比,消費者最終只拿到過濾後的地址列表。
  • 如果匹配條件爲空,表示對所有消費方應用,如:=> host != 10.20.153.11
  • 如果過濾條件爲空,表示禁止訪問,如:host = 10.20.153.10 =>

參數支持:

  • 服務調用信息,如:method, argument 等,暫不支持參數路由
  • URL 本身的字段,如:protocol, host, port 等
  • 以及 URL 上的所有參數,如:application, organization 等

條件支持:

  • 等號 = 表示"匹配",如:host = 10.20.153.10
  • 不等號 != 表示"不匹配",如:host != 10.20.153.10

值支持:

  • 以逗號 , 分隔多個值,如:host != 10.20.153.10,10.20.153.11
  • 以星號 * 結尾,表示通配,如:host != 10.20.*
  • 以美元符 $ 開頭,表示引用消費者參數,如:host = $host

3.3 ScriptRouter

ScriptRouter:腳本路由規則,腳本路由規則支持 JDK 腳本引擎的所有腳本,比如:javascript, jruby, groovy 等,通過 type=javascript 參數設置腳本類型,缺省爲 javascript。

基於腳本引擎的路由規則,如:

(function route(invokers) {
    var result = new java.util.ArrayList(invokers.size());
    for (i = 0; i < invokers.size(); i ++) {
        if ("10.20.153.10".equals(invokers.get(i).getUrl().getHost())) {
            result.add(invokers.get(i));
        }
    }
    return result;
} (invokers)); // 表示立即執行方法

4、Route 功能

通過配置不同的 Route 規則,我們可以實現以下功能。

  1. 排除預發佈機:

     => host != 172.22.3.91
    
    
  2. 白名單:

     host != 10.20.153.10,10.20.153.11 =>
    
    
  3. 黑名單:

     host = 10.20.153.10,10.20.153.11 =>
    
    
  4. 服務寄宿在應用上,只暴露一部分的機器,防止整個集羣掛掉:

     => host = 172.22.3.1*,172.22.3.2*
    
    
  5. 爲重要應用提供額外的機器:

     application != kylin => host != 172.22.3.95,172.22.3.96
    
    
  6. 讀寫分離:

     method = find*,list*,get*,is* => host = 172.22.3.94,172.22.3.95,172.22.3.96
     method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98
    
    
  7. 前後臺分離:

     application = bops => host = 172.22.3.91,172.22.3.92,172.22.3.93
     application != bops => host = 172.22.3.94,172.22.3.95,172.22.3.96
    
    
  8. 隔離不同機房網段:

     host != 172.22.3.* => host != 172.22.3.*
    
    
  9. 提供者與消費者部署在同集羣內,本機只訪問本機的服務:

     => host = $host
    

參考文章:

1.http://en.wikipedia.org/wiki/Routing
2.http://dubbo.apache.org/books/dubbo-user-book/demos/routing-rule.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章