Dubbo 系列(07-5)集群容錯 - Mock
Spring Cloud Alibaba 系列目錄 - Dubbo 篇
1. 背景介紹
相關文檔推薦:
Dubbo 的集群容錯中默認會組裝 MockClusterWrapper,它實現了 Dubbo 的服務降級和本地偽裝。
1.1 服務降級
服務降級配置方式,更多參考官網 Dubbo 實戰 - 服務降級
<dubbo:reference interface="com.foo.BarService" mock="force:return+null"/>
或向注冊中心寫入動態配置覆蓋規則:
"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 實戰 - 本地偽裝
<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。
總結: Dubbo Mock 主要流程如下:
MockClusterWrapper
:由於這個類是 Cluster 的包裝類,所以 Dubbo 默認裝配 MockClusterWrapper,對 ClusterInvoker 進行包裝。MockClusterInvoker
:核心類,對 ClusterInvoker 進行包裝,主要功能:一是判斷是否需要開啟 Mock 機制;二是根據 MockInvokersSelector 過濾出對應的 Mock Invoker;三是執行 MockInvoker。MockInvokersSelector
:Mock 路由策略,由於是 @Activate 修辭,因此會自動裝配。當不開啟 Mock 時返回正常的 Invoker,當開啟了 Mock 后返回 Mock Invoker。MockProtocol
:創建 MockInvoker。這個 MockProtocol 只能引用,不能暴露。MockInvoker
:核心類,真正執行服務降級,處理mock="return null"
、mock="throw com.foo.MockException"
、mock="com.foo.BarServiceMock"
。
2.2 MockClusterInvoker
MockClusterInvoker 的主要功能是判斷是否需要開啟 Mock 機制,如果開啟 Mock 則需要過濾出 MockInvoker 后執行服務降級。MockClusterWrapper 和 MockClusterInvoker 位於 dubbo-cluster
工程下。
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 的核心類,主要功能有三個:
- 判斷是否需要開啟 Mock 機制,由 invoke 方法完成。
- 根據 MockInvokersSelector 過濾出對應的 Mock Invoker,由 selectMockInvoker 完成,實際是委托給 MockInvokersSelector 完成路由。
- 執行 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 時,有三種情況:
- attachments 為 null 或 invocation.need.mock 為 null,則返回
非MockedInvoker
。 invocation.need.mock=true
則返回MockedInvoker
。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,最后執行服務降級。
- 根據服務降級配置,執行對應的服務降級,如
return
、throw
、xxxServiceMock
- 直接 return 需要解析返回參數:parseMockValue
- 執行
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 服務降級有三種處理情況:
return
:直接返回,可以是 empty、null 、true 、false 、json 格式,由方式 parseMockValue 進行解析。throw
:直接拋出異常。如果沒有指定異常,拋出 RpcException,否則拋出指定的 Exception。xxxServiceMock
:執行 xxxServiceMock 方法。如果mock=true
或mock=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 后返回。
每天用心記錄一點點。內容也許不重要,但習慣很重要!