Dubbo 系列(07-5)集羣容錯 - Mock

Dubbo 系列(07-5)集羣容錯 - Mock

[toc]html

Spring Cloud Alibaba 系列目錄 - Dubbo 篇

1. 背景介紹

相關文檔推薦:java

  1. Dubbo 實戰 - 服務降級
  2. Dubbo 實戰 - 本地假裝
  3. Dubbo 實戰 - 本地存根

Dubbo 的集羣容錯中默認會組裝 MockClusterWrapper,它實現了 Dubbo 的服務降級和本地假裝。spring

1.1 服務降級

服務降級配置方式,更多參考官網 Dubbo 實戰 - 服務降級apache

<dubbo:reference interface="com.foo.BarService" mock="force:return+null"/>

或向註冊中心寫入動態配置覆蓋規則:json

"override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"
  • mock=force:return+null 表示消費方對該服務的方法調用都直接返回 null 值,不發起遠程調用。用來屏蔽不重要服務不可用時對調用方的影響。
  • mock=fail:return+null 表示消費方對該服務的方法調用在失敗後,再返回 null 值,不拋異常。用來容忍不重要服務不穩定時對調用方的影響。

1.2 本地假裝

本地假裝配置方式,更多參考官網 Dubbo 實戰 - 本地假裝api

<dubbo:reference interface="com.foo.BarService" mock="true"/>
<dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock"/>
<dubbo:reference interface="com.foo.BarService" mock="return null"/>
<dubbo:reference interface="com.foo.BarService" mock="throw com.foo.MockException" />

以上幾種方式,和 mock=fail:return+null 同樣,表示消費方對該服務的方法調用在失敗後,執行 mock 配置的代碼。緩存

2. 源碼分析

2.1 原理分析

在上一篇講解 Dubbo Cluster 時能夠看到, Dubbo 默認會將 Cluster#join 生成的 ClusterInvoker 對象包裝 MockClusterInvoker。app

圖1 Dubbo Mock原理圖
graph LR MockClusterWrapper -- join --> MockClusterInvoker MockClusterInvoker -- selectMockInvoker --> MockInvokersSelector MockInvokersSelector -- route --> MockProtocol MockProtocol -- refer --> MockInvoker

總結: Dubbo Mock 主要流程以下:dom

  1. MockClusterWrapper:因爲這個類是 Cluster 的包裝類,因此 Dubbo 默認裝配 MockClusterWrapper,對 ClusterInvoker 進行包裝。
  2. MockClusterInvoker:核心類,對 ClusterInvoker 進行包裝,主要功能:一是判斷是否須要開啓 Mock 機制;二是根據 MockInvokersSelector 過濾出對應的 Mock Invoker;三是執行 MockInvoker。
  3. MockInvokersSelector:Mock 路由策略,因爲是 @Activate 修辭,所以會自動裝配。當不開啓 Mock 時返回正常的 Invoker,當開啓了 Mock 後返回 Mock Invoker。
  4. MockProtocol:建立 MockInvoker。這個 MockProtocol 只能引用,不能暴露。
  5. MockInvoker:核心類,真正執行服務降級,處理 mock="return null"、mock="throw com.foo.MockException"mock="com.foo.BarServiceMock"

2.2 MockClusterInvoker

MockClusterInvoker 的主要功能是判斷是否須要開啓 Mock 機制,若是開啓 Mock 則須要過濾出 MockInvoker 後執行服務降級。MockClusterWrapper 和 MockClusterInvoker 位於 dubbo-cluster 工程下。ide

2.2.1 MockClusterWrapper

MockClusterWrapper 是包裝類,按 Dubbo SPI 機制,會將默認的 Cluster 進行包裝。

public class MockClusterWrapper implements Cluster {
    private Cluster cluster;
    public MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory, this.cluster.join(directory));
    }
}

總結: Dubbo 默認的 Cluster 是 FailoverCluster,也就是說 MockClusterWrapper 會對 FailoverCluster 進行包裝。接下來看一下 Mock 的核心 MockClusterInvoker 執行過程。

MockClusterInvoker 是 Dubbo Mock 的核心類,主要功能有三個:

  1. 判斷是否須要開啓 Mock 機制,由 invoke 方法完成。
  2. 根據 MockInvokersSelector 過濾出對應的 Mock Invoker,由 selectMockInvoker 完成,實際是委託給 MockInvokersSelector 完成路由。
  3. 執行 MockInvoker,由 doMockInvoke方法完成,實際是委託給 MockInvoker。

2.2.2 invoke 執行入口

invoke 判斷是否須要開啓 Mock 機制,若是須要開啓,則調用 doMockInvoke 進行服務降級。

@Override
public Result invoke(Invocation invocation) throws RpcException {
    Result result = null;
    String value = directory.getUrl().getMethodParameter(invocation.getMethodName(),
		MOCK_KEY, Boolean.FALSE.toString()).trim();
    if (value.length() == 0 || value.equalsIgnoreCase("false")) {
        //no mock
        result = this.invoker.invoke(invocation);
    } else if (value.startsWith("force")) {
        //force:direct mock
        result = doMockInvoke(invocation, null);
    } else {
        //fail-mock
        try {
            result = this.invoker.invoke(invocation);
        } catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            }
            result = doMockInvoke(invocation, e);
        }
    }
    return result;
}

總結: **invoke 關注一個問題,是否須要開啓 Mock,若是開啓 Mock 調用 doMockInvoke 執行。**代碼註釋已經很清楚了,分別對 no mock:(正常流程)force:(強制mock)fail:(失敗mock,默認) 分別處理。若是 mock=false 則正常處理,若是配置 mock="return null"mock="fail:return+null" 處理流程是同樣的。

2.2.3 doMockInvoke

doMockInvoke 執行服務降級。

private Result doMockInvoke(Invocation invocation, RpcException e) {
    Result result = null;
    Invoker<T> minvoker;

    // 1. 過濾能夠用 mockInvokers
    List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
    // 2. 若是沒有,建立 MockInvoker
    if (CollectionUtils.isEmpty(mockInvokers)) {
        minvoker = (Invoker<T>) new MockInvoker(directory.getUrl(), directory.getInterface());
    } else {
        minvoker = mockInvokers.get(0);
    }
    // 3. 執行服務降級 mockInvoker
    try {
        result = minvoker.invoke(invocation);
    } catch (RpcException me) {
        if (me.isBiz()) {
            result = AsyncRpcResult.newDefaultAsyncResult(me.getCause(), invocation);
        } else {
            throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause());
        }
    } catch (Throwable me) {
        throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
    }
    return result;
}

總結: doMockInvoke 最終調用 minvoker.invoke(invocation) 進行服務降級,其中須要關注的是 selectMockInvoker(invocation) 過濾緩存中的 MockInvoker,若是沒有就須要建立新的 MockInvoker。

2.2.4 selectMockInvoker

selectMockInvoker 方法很奇怪,沒有看到真正的 MockInvoker 過濾究竟是怎麼完成的。實際上 Dubbo 的默認路由策略就包含了 MockInvokersSelector,由這個類完成規則路由。

private List<Invoker<T>> selectMockInvoker(Invocation invocation) {
   List<Invoker<T>> invokers = null;
   if (invocation instanceof RpcInvocation) {
       // 1. 設置invocation.need.mock=true
       ((RpcInvocation) invocation).setAttachment(INVOCATION_NEED_MOCK, Boolean.TRUE.toString());
       // 2. 調用 MockInvokersSelector 路由規則過濾服務列表
       invokers = directory.list(invocation);
       ...
   }
   return invokers;
}

總結: selectMockInvoker 方法偷偷在將 invocation 的 invocation.need.mock 屬性設置爲 false,這個參數在 MockInvokersSelector 中就頗有用了。而後經過 directory.list(invocation) 方法從新獲取服務列表,在 Dubbo 系列(07-1)集羣容錯 - 服務字典 分析 RegisterDirectory 源碼時,咱們知道 list 方法會調用 routeChain.route 路由規則過濾服務。 下面看一下 MockInvokersSelector 代碼。

2.3 MockInvokersSelector

MockInvokersSelector 在未開啓 Mock 時返回正常的 Invokers,開啓後返回 MockInvoker。

@Override
public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
		URL url, final Invocation invocation) throws RpcException {
    if (CollectionUtils.isEmpty(invokers)) {
        return invokers;
    }

    if (invocation.getAttachments() == null) {
        // 1. 返回 -> 非MockedInvoker
        return getNormalInvokers(invokers);
    } else {
        String value = invocation.getAttachments().get(INVOCATION_NEED_MOCK);
        if (value == null) {
            return getNormalInvokers(invokers);
        // 2. invocation.need.mock=true則返回 -> MockedInvoker(MockProtocol)
        } else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
            return getMockedInvokers(invokers);
        }
    }
    // 3. invocation.need.mock=false則返回 -> 非MockedInvoker + MockedInvoker
    // ???
    return invokers;
}

**總結:**directory.list 調用 MockInvokersSelector.route 時,有三種狀況:

  1. attachments 爲 null 或 invocation.need.mock 爲 null,則返回 非MockedInvoker
  2. invocation.need.mock=true 則返回 MockedInvoker
  3. invocation.need.mock=false 則返回 非MockedInvoker + MockedInvoker ???

2.4 MockInvoker

MockProtocol 和 MockInvoker 位於 dubbo-rpc-api 工程下。

在 MockClusterInvoker#doMockInvoke 方法中,若是 directory.list 過濾出的 MockedInvoker 爲空,則會直接建立一個 MockedInvoker,代碼以下:

minvoker = (Invoker<T>) new MockInvoker(directory.getUrl(), directory.getInterface());

其實 Mock 也是一種協議,能夠在註冊中心 /dubbo/com.foo.BarService/providers 目錄下寫入:

"mock://192.168.139.101/com.foo.BarService"

這樣消費者訂閱 com.foo.BarService 服務後會根據 MockProtocol 協議建立對應的 MockedInvoker。

2.4.1 MockProtocol

MockProtocol 只能經過 reference 引入,不能經過 export 暴露服務。其實也就是直接建立了一個 MockInvoker。

final public class MockProtocol extends AbstractProtocol {
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        throw new UnsupportedOperationException();
    }

    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> type, URL url) throws RpcException {
        return new MockInvoker<>(url, type);
    }
}

總結: MockProtocol 很是簡單,就很少說了。下面看一下 MockInvoker 代碼。

2.4.2 MockInvoker

MockInvoker 執行服務降級。在 MockClusterInvoker 判斷是否須要開啓 Mock 後,MockInvokersSelector 過濾出可用的 MockInvoker,最後執行服務降級。

  1. 根據服務降級配置,執行對應的服務降級,如 returnthrowxxxServiceMock
  2. 直接 return 須要解析返回參數:parseMockValue
  3. 執行 xxxServiceMock 須要查找對應的實現類:getInvoker。

先看一下總體的執行流程 invoke 方法。

@Override
public Result invoke(Invocation invocation) throws RpcException {
    // 1. 獲取mock值,URL 中 methodname.mock 或 mock 參數
    String mock = getUrl().getParameter(invocation.getMethodName() + "." + MOCK_KEY);
    if (invocation instanceof RpcInvocation) {
        ((RpcInvocation) invocation).setInvoker(this);
    }
    if (StringUtils.isBlank(mock)) {
        mock = getUrl().getParameter(MOCK_KEY);
    }

    if (StringUtils.isBlank(mock)) {
        throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
    }
    
    // 2. 對mock字符串進行處理,好比去除 `force:`、`fail:` 前綴
    mock = normalizeMock(URL.decode(mock));
    // 3. return
    if (mock.startsWith(RETURN_PREFIX)) {
        mock = mock.substring(RETURN_PREFIX.length()).trim();
        try {
            Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
            Object value = parseMockValue(mock, returnTypes);
            return AsyncRpcResult.newDefaultAsyncResult(value, invocation);
        } catch (Exception ew) {
            throw new RpcException(ew);
        }
    // 3. throw
    } else if (mock.startsWith(THROW_PREFIX)) {
        mock = mock.substring(THROW_PREFIX.length()).trim();
        if (StringUtils.isBlank(mock)) {
            throw new RpcException("mocked exception for service degradation.");
        } else { // user customized class
            Throwable t = getThrowable(mock);
            throw new RpcException(RpcException.BIZ_EXCEPTION, t);
        }
    // 5. xxxServiceMock
    } else { //impl mock
        try {
            Invoker<T> invoker = getInvoker(mock);
            return invoker.invoke(invocation);
        } catch (Throwable t) {
            throw new RpcException("Failed to create mock implementation class " + mock, t);
        }
    }
}

總結: invoke 執行服務降級,首先獲取 mock 參數,並對 mock 參數進行處理,如去除 force:fail: 前綴。Dubbo 服務降級有三種處理狀況:

  1. return:直接返回,能夠是 empty、null 、true 、false 、json 格式,由方式 parseMockValue 進行解析。
  2. throw:直接拋出異常。若是沒有指定異常,拋出 RpcException,不然拋出指定的 Exception。
  3. xxxServiceMock:執行 xxxServiceMock 方法。若是 mock=truemock=defalut 則查找 xxxServiceMock 方法後執行,若是 mock=com.dubbo.testxxxService 則執行指定的方法。

getInvoker 方法查找指定對流的 Invoker。

private Invoker<T> getInvoker(String mockService) {
    // 1. 緩存命中
    Invoker<T> invoker = (Invoker<T>) MOCK_MAP.get(mockService);
    if (invoker != null) {
        return invoker;
    }
	
    // 2. 根據serviceType查找mock的實現類,默認爲 xxxServiceMock
    Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface());
    T mockObject = (T) getMockObject(mockService, serviceType);
    // 3. 包裝成Invoker
    invoker = PROXY_FACTORY.getInvoker(mockObject, serviceType, url);
    if (MOCK_MAP.size() < 10000) {
        MOCK_MAP.put(mockService, invoker);
    }
    return invoker;
}

public static Object getMockObject(String mockService, Class serviceType) {
    // mock=true或default時,查找 xxxServiceMock
    if (ConfigUtils.isDefault(mockService)) {
        mockService = serviceType.getName() + "Mock";
    }
	// mock=testxxxService,指定Mock實現類
    Class<?> mockClass = ReflectUtils.forName(mockService);
    ...
    return mockClass.newInstance();
}

總結: Dubbo 若是不指定 Mock 實現類,默認查找 xxxServiceMock。若是存在該實現類,則將其包裝成 Invoker 後返回。


天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索