Dubbo架構設計與原始碼解析(三)責任鏈模式

京東雲開發者發表於2022-12-23
作者:周可強

一、責任鏈模式簡介

1、責任鏈模式定義

責任鏈(Chain of Responsibility)模式的定義:為了避免請求傳送者與多個請求處理者耦合在一起,於是將所有請求的處理者透過前一物件記住其下一個物件的引用而連成一條鏈;當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止。在責任鏈模式中,客戶只需要將請求傳送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,請求會自動進行傳遞。所以責任鏈將請求的傳送者和請求的處理者解耦了。

2、責任鏈特點

責任鏈模式是一種物件行為型模式,

其主要優點如下。

1).降低了物件之間的耦合度。該模式使得一個物件無須知道到底是哪一個物件處理其請求以及鏈的結構,傳送者和接收者也無須擁有對方的明確資訊。

2).增強了系統的可擴充套件性。可以根據需要增加新的請求處理類,滿足開閉原則。

3).增強了給物件指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。責任鏈簡化了物件之間的連線。每個物件只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if···else 語句。

4).責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個物件完成,明確各類的責任範圍,符合類的單一職責原則。



其主要缺點如下。

1).不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。

2).對比較長的職責鏈,請求的處理可能涉及多個處理物件,系統效能將受到一定影響。

3).職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設定而導致系統出錯,如可能會造成迴圈呼叫。

3、責任鏈結構圖











二、Dubbo中的責任鏈模式

1、過濾器日誌

透過列印過濾器的日誌,我們可以看到在釋出服務的過程中,會依次經過dubbo的每個過濾器類,以此來保證服務的完善。





2、過濾器簡圖

dubbo透過將每個過濾器類filter封裝成dubbo的核心模型invoker進行組裝,最終形成晚上的過濾器責任鏈filterChain。





3、過濾器類圖

Protocol是核心模型invoker暴露和引用的主功能入口,採用SPI的介面,他的兩個方法export和refer分別對應provider和consumer端的服務功能,ProtocolFilterWapper則是Dubbo的過濾器的主要實現類,透過重寫的export和refer指向buildInvokerChain方法,在buildInvokerChain中進行責任鏈的獲取與組裝,在extensionLoader中透過SPI獲取Filter的各實現類,並透過ActivateComparator進行排序,最終形成完整的責任鏈。





三、Dubbo中各Filter責任介紹

1、provider用到的filter





2、consumer用到的filter





四、原始碼探析

進入到核心類ProtocolFilterWrapper中,在實現類中export和refer,都採用相同的構造責任鏈方法buildInvokerChain,只是透過引數group進行區分





在buildInvokerChain中,透過getActivateExtension獲取過濾器陣列,並在之後封裝成核心模型invoker並組裝成責任鏈

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        // 獲得過濾器陣列 (已經排好序的)
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        // 建立帶 Filter 鏈的 Invoker 物件
        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        return filter.invoke(next, invocation);
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        System.out.println("group:" + group);
        for (Filter filter : filters) {
            System.out.println(filter.getClass());
        }
        return last;
    }

getActivateExtension是主要的組裝邏輯,他包含獲取與排序等邏輯

首先進行判斷是否採用系統預設的Filter過濾器,並對每一個系統過濾器進行校驗是否移除,然後對系統過濾器排序,再透過指定的引數,增加使用者自定義的過濾器組裝責任鏈


public List<T> getActivateExtension(URL url, String key, String group) {
        // 從 Dubbo URL 獲得引數值
        String value = url.getParameter(key);
        // 獲得符合自動啟用條件的擴充物件陣列
        return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
    }
public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<T>();
        //所有使用者自己配置的filter資訊(有些Filter是預設啟用的,有些是配置啟用的,這裡的names就指的配置啟用的filter資訊)
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
        // 處理自動啟用的擴充物件們
        // 判斷不存在配置 `"-name"` 。例如,<dubbo:service filter="-default" /> ,代表移除所有預設過濾器。
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
            // 獲得擴充實現類陣列
            getExtensionClasses();
            // 迴圈
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                //name指的是SPI讀取的配置檔案的key
                String name = entry.getKey();
                Activate activate = entry.getValue();
                if (isMatchGroup(group, activate.group())) { // 匹配分組
                    // 獲得擴充物件
                    T ext = getExtension(name);
                    if (!names.contains(name) // 不包含在自定義配置裡。如果包含,會在下面的程式碼處理。
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name) // 判斷是否配置移除。例如 <dubbo:service filter="-monitor" />,則 MonitorFilter 會被移除
                            && isActive(activate, url)) { // 判斷是否啟用
                        exts.add(ext);
                    }
                }
            }
            // 排序
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        // 處理自定義配置的擴充物件們。例如在 <dubbo:service filter="demo" /> ,代表需要加入 DemoFilter
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 判斷非移除的
                // 將配置的自定義在自動啟用的擴充物件們前面。例如,<dubbo:service filter="demo,default,demo2" /> ,則 DemoFilter 就會放在預設的過濾器前面。
                if (Constants.DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    // 獲得擴充物件
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        // 新增到結果集
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }

系統預設的過濾器和udf過濾器進行區分

以ContextFilter為例,系統預設過濾器包含Activate註解,用於指定所屬分組與排序權重,使用者自己實現的過濾器則不能新增Activate註解透過釋出時指定所需的過濾器







我們看下具體的排序比較方法,首先判斷Activate註解是否指定before和after引數用來指定排序,若不存在則採用order權重進行排序

ActivateComparator.class
public int compare(Object o1, Object o2) {
        // 基本排序
        if (o1 == null && o2 == null) {
            return 0;
        }
        if (o1 == null) {
            return -1;
        }
        if (o2 == null) {
            return 1;
        }
        if (o1.equals(o2)) {
            return 0;
        }

        Activate a1 = o1.getClass().getAnnotation(Activate.class);
        Activate a2 = o2.getClass().getAnnotation(Activate.class);

        // 使用註解的 `after` 和 `before` 屬性,排序
        if ((a1.before().length > 0 || a1.after().length > 0 || a2.before().length > 0 || a2.after().length > 0) // (a1 或 a2) 存在 (`after` 或 `before`) 屬性。
                && o1.getClass().getInterfaces().length > 0 && o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) { // 實現的介面,有 @SPI 註解。
            // 獲得擴充載入器
            ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
            // 以 a1 的視角,進行一次比較
            if (a1.before().length > 0 || a1.after().length > 0) {
                String n2 = extensionLoader.getExtensionName(o2.getClass());
                for (String before : a1.before()) {
                    if (before.equals(n2)) {
                        return -1;
                    }
                }
                for (String after : a1.after()) {
                    if (after.equals(n2)) {
                        return 1;
                    }
                }
            }
            // 以 a2 的視角,進行一次比較。
            if (a2.before().length > 0 || a2.after().length > 0) {
                String n1 = extensionLoader.getExtensionName(o1.getClass());
                for (String before : a2.before()) {
                    if (before.equals(n1)) {
                        return 1;
                    }
                }
                for (String after : a2.after()) {
                    if (after.equals(n1)) {
                        return -1;
                    }
                }
            }
        }

        // 使用註解的 `order` 屬性,排序。
        int n1 = a1 == null ? 0 : a1.order();
        int n2 = a2 == null ? 0 : a2.order();
        // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
        return n1 > n2 ? 1 : -1;
    }

總結:
責任鏈模式是設計模式中簡單且常見的設計模式,可能我們日常中也會經常應用責任鏈模式,dubbo中的責任鏈模式將靈活性發揮的很充分,不論是從分組概念、透過註解指定排序的優先順序、每個filter的是否移除 等,將每個filter做成了可插拔的,減少對程式碼的侵入性,這點是非常值得我們學習的。

相關文章