美文网首页程序员技术干货
Dubbo优雅服务降级之mock

Dubbo优雅服务降级之mock

作者: 3c69b7c624d9 | 来源:发表于2017-11-30 22:58 被阅读323次

[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字段

  1. 不包含字段直接调用远程服务
  2. 包含MOck字段并且值为force打头那么直接调用Mock操作
  3. 否则优先执行远程调用,如果调用失败且返回的是非业务异常再调用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

相关文章

  • Dubbo优雅服务降级之Stub和回声服务

    上篇Dubbo优雅服务降级之mock描述了关于mock的细节。此篇就详述一下关于Stub的实现。 在dubbo的官...

  • Dubbo优雅服务降级之mock

    [Dubbo优雅服务降级之Stub][Dubbo_Stub] dubbo作为国内互联网最常用的Java开源服务治理...

  • dubbo记录

    目录 《dubbo入门》《dubbo管理控制台》《dubbo超时重传》《dubbo直连》《dubbo实现服务降级》...

  • Dubbo之降级Mock源码分析

    熔断与降级 说到降级必须扯到熔断。这两个技术既可以独立使用也可以一起使用。下面从我的理解来说下它们的区别。 熔断一...

  • Dubbo之服务降级分析

    当我们遇到高并发下的流量激增问题时,常常提到降级、熔断和限流的概念。我先简单讲解一下这三个概念的定义。 相关概念 ...

  • 【dubbo源码】18. 服务消费方:集群容错之本地伪装mock

    本地伪装使用方式 如何在 Dubbo 中利用本地伪装实现服务降级 本地伪装通常用于服务降级,比如某验权服务,当服务...

  • Dubbo配置参数详解-mock

    Dubbo配置参数详解-mock Dubbo是一个由阿里开源的服务治理框架,笔者的公司重度使用Dubbo。Dubb...

  • dubbo高可用

    1、注册中心宕机与dubbo直连 2、负载均衡(4种) 3、服务降级 4、服务容错

  • Dubbo服务之Stub和Mock

    Dubbo服务中包含两个功能,感觉还是蛮有意思的,一个是本地存根,一个是本地伪装。学习了文末的参考文章,感觉说的很...

  • 大规模分布式系统原理解析和架构实践

    1 架构词汇 1.1 高可用 负载均衡(负载均衡算法) 反向代理 服务隔离 服务限流 服务降级(自动优雅降级) 大...

网友评论

    本文标题:Dubbo优雅服务降级之mock

    本文链接:https://www.haomeiwen.com/subject/vznbbxtx.html