title: Dubbo優雅服務降級之mock tags:
- dubbo
- mock
- 服務降級 categories: 工作日誌 date: 2017-06-25 18:18:52
[Dubbo優雅服務降級之Stub][Dubbo_Stub]
dubbo作為國內網際網路最常用的Java開源服務治理框架,在提供了遠端呼叫的同時也提供了服務降級功能。
首先可以考慮一下服務降級的需求===》考慮在系統服務呼叫失敗時可以返回指定訊息而不是異常
通常來說選用dubbo的Mock功能可以實現。
在上一篇中描述到MockClusterWrapper幾乎是必須的操作,實質上Mock也是通過其生成的MockClusterInvoker來實現。
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.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")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
} else {
//fail-mock
try {
result = this.invoker.invoke(invocation);
}catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
複製程式碼
在判斷對應URL中mock欄位
- 不包含欄位直接呼叫遠端服務
- 包含MOck欄位並且值為force打頭那麼直接呼叫Mock操作
- 否則優先執行遠端呼叫,如果呼叫失敗且返回的是非業務異常再呼叫Mock操作
呼叫Mock操作分為如下幾步
@SuppressWarnings({ "unchecked", "rawtypes" })
private Result doMockInvoke(Invocation invocation,RpcException e){
Result result = null;
Invoker<T> minvoker ;
List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
if (mockInvokers == null || mockInvokers.size() == 0){
minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
} else {
minvoker = mockInvokers.get(0);
}
try {
result = minvoker.invoke(invocation);
} catch (RpcException me) {
if (me.isBiz()) {
result = new RpcResult(me.getCause());
} else {
throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause());
}
//
} catch (Throwable me) {
throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
}
return result;
}
/**
* 返回MockInvoker
* 契約:
* directory根據invocation中是否有Constants.INVOCATION_NEED_MOCK,來判斷獲取的是一個normal invoker 還是一個 mock invoker
* 如果directorylist 返回多個mock invoker,只使用第一個invoker.
* @param invocation
* @return
*/
private List<Invoker<T>> selectMockInvoker(Invocation invocation){
//TODO generic invoker?
if (invocation instanceof RpcInvocation){
//存在隱含契約(雖然在介面宣告中增加描述,但擴充套件性會存在問題.同時放在attachement中的做法需要改進
((RpcInvocation)invocation).setAttachment(Constants.INVOCATION_NEED_MOCK, Boolean.TRUE.toString());
//directory根據invocation中attachment是否有Constants.INVOCATION_NEED_MOCK,來判斷獲取的是normal invokers or mock invokers
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
} else {
return null ;
}
}
```
1. 首先找到對應的 可以呼叫的invoker,並且標記為mock
2. 如果無法找到invoker那麼直接新建MockInvoker
3. 呼叫invoker,當呼叫MockInvoker發生下列行為
```java
public Result invoke(Invocation invocation) throws RpcException {
String mock = getUrl().getParameter(invocation.getMethodName()+"."+Constants.MOCK_KEY);
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(this);
}
if (StringUtils.isBlank(mock)){
mock = getUrl().getParameter(Constants.MOCK_KEY);
}
if (StringUtils.isBlank(mock)){
throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
}
mock = normallizeMock(URL.decode(mock));
if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())){
RpcResult result = new RpcResult();
result.setValue(null);
return result;
} else if (mock.startsWith(Constants.RETURN_PREFIX)) {
mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
mock = mock.replace('`', '"');
try {
Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
Object value = parseMockValue(mock, returnTypes);
return new RpcResult(value);
} catch (Exception ew) {
throw new RpcException("mock return invoke error. method :" + invocation.getMethodName() + ", mock:" + mock + ", url: "+ url , ew);
}
} else if (mock.startsWith(Constants.THROW_PREFIX)) {
mock = mock.substring(Constants.THROW_PREFIX.length()).trim();
mock = mock.replace('`', '"');
if (StringUtils.isBlank(mock)){
throw new RpcException(" mocked exception for Service degradation. ");
} else { //使用者自定義類
Throwable t = getThrowable(mock);
throw new RpcException(RpcException.BIZ_EXCEPTION, t);
}
} else { //impl mock
try {
Invoker<T> invoker = getInvoker(mock);
return invoker.invoke(invocation);
} catch (Throwable t) {
throw new RpcException("Failed to create mock implemention class " + mock , t);
}
}
}
```
將mock引數對應的值進行解析,分為下列幾種
//mock=fail:throw
//mock=fail:return
//mock=xx.Service
```java
private String normallizeMock(String mock) {
if (mock == null || mock.trim().length() ==0){
return mock;
} else if (ConfigUtils.isDefault(mock) || "fail".equalsIgnoreCase(mock.trim()) || "force".equalsIgnoreCase(mock.trim())){
mock = url.getServiceInterface()+"Mock";
}
if (mock.startsWith(Constants.FAIL_PREFIX)) {
mock = mock.substring(Constants.FAIL_PREFIX.length()).trim();
} else if (mock.startsWith(Constants.FORCE_PREFIX)) {
mock = mock.substring(Constants.FORCE_PREFIX.length()).trim();
}
return mock;
}
```
其實分為force或者fail打頭的型別,如果不是則直接使用對應<Interface>Mock的型別為Mock類(方便自定義相關返回以及降級)。
為了便於操作提供了force或者fail指令
```java
public static Object parseMockValue(String mock, Type[] returnTypes) throws Exception {
Object value = null;
if ("empty".equals(mock)) {
value = ReflectUtils.getEmptyObject(returnTypes != null && returnTypes.length > 0 ? (Class<?>)returnTypes[0] : null);
} else if ("null".equals(mock)) {
value = null;
} else if ("true".equals(mock)) {
value = true;
} else if ("false".equals(mock)) {
value = false;
} else if (mock.length() >=2 && (mock.startsWith("\"") && mock.endsWith("\"")
|| mock.startsWith("\'") && mock.endsWith("\'"))) {
value = mock.subSequence(1, mock.length() - 1);
} else if (returnTypes !=null && returnTypes.length >0 && returnTypes[0] == String.class) {
value = mock;
} else if (StringUtils.isNumeric(mock)) {
value = JSON.parse(mock);
}else if (mock.startsWith("{")) {
value = JSON.parse(mock, Map.class);
} else if (mock.startsWith("[")) {
value = JSON.parse(mock, List.class);
} else {
value = mock;
}
if (returnTypes != null && returnTypes.length > 0) {
value = PojoUtils.realize(value, (Class<?>)returnTypes[0], returnTypes.length > 1 ? returnTypes[1] : null);
}
return value;
}
```
mock可以返回false,null等,常用mock=fail:return null
那麼就可以實現在一些不重要的服務掛掉的時候強制返回null,如果客戶端對應相容此操作自然可以在客戶端不報錯,繼續正常的業務流程(如果精細化控制可以使用Mock類)
```java
private Invoker<T> getInvoker(String mockService){
Invoker<T> invoker =(Invoker<T>) mocks.get(mockService);
if (invoker != null ){
return invoker;
} else {
Class<T> serviceType = (Class<T>)ReflectUtils.forName(url.getServiceInterface());
if (ConfigUtils.isDefault(mockService)) {
mockService = serviceType.getName() + "Mock";
}
Class<?> mockClass = ReflectUtils.forName(mockService);
if (! serviceType.isAssignableFrom(mockClass)) {
throw new IllegalArgumentException("The mock implemention class " + mockClass.getName() + " not implement interface " + serviceType.getName());
}
if (! serviceType.isAssignableFrom(mockClass)) {
throw new IllegalArgumentException("The mock implemention class " + mockClass.getName() + " not implement interface " + serviceType.getName());
}
try {
T mockObject = (T) mockClass.newInstance();
invoker = proxyFactory.getInvoker(mockObject, (Class<T>)serviceType, url);
if (mocks.size() < 10000) {
mocks.put(mockService, invoker);
}
return invoker;
} catch (InstantiationException e) {
throw new IllegalStateException("No such empty constructor \"public " + mockClass.getSimpleName() + "()\" in mock implemention class " + mockClass.getName(), e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
```
Mock類的呼叫也很簡單,直接呼叫對應型別名的對應Interface的建構函式===》此處沒有依賴注入
至此Mock部分的分析結束了。
[Dubbo_Stub]: https://my.oschina.net/qixiaobo025/blog/1014845
複製程式碼