dubbo中的mock实现机制

Mock是SOA之中的一个很有用的功能,不仅可以用来进行服务降级,也可以用来在测试中模拟服务调用的各种异常情况。dubbo框架里面的mock是在服务使用者这一端实现的,下面对实现机制进行分析:

1. Mock的植入

很显然,既然提供了mock机制,那么mock应该作为一个环节插入到服务使用者的整个处理流程之中,而dubbo的设计基本采用了装饰器模式,一层一层的进行包装,这个具体的植入点就在RegistryProctocol通过Cluster来创建ClusterInvoker的时候:

RegistryProctocol的doRefer方法:

[java] view plain copy

  1. return cluster.createClusterInvoker(registryDirectory);  

 cluster的类型为Cluster$Adaptive,这实际上是一个通用的代理类,它会根据regsitryDirectory的getConsumerUrl方法返回的Url中的cluster参数的值来定位到实际的Cluster的实现类上,如果Url之中没有指定cluster,那么会采用Cluster的SPI注解上配置的默认值FailoverCluster.NAME,也就是默认情况下会调用ExtensionLoader<Clsuter>内部的key为failover的实例:

   

[java] view plain copy

  1. @SPI(FailoverCluster.NAME)  
  2. public interface Cluster  
  3. {  


在dubbo的配置文件  classpath:/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.Cluster中,failover对应的是FailoverCluster类: 

[java] view plain copy

  1. mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper  
  2. failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster  
  3. failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster  
  4. failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster  
  5. failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster  
  6. forking=com.alibaba.dubbo.rpc.cluster.support.ForkingCluster  
  7. available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster  
  8. switch=com.alibaba.dubbo.rpc.cluster.support.SwitchCluster  
  9. mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster  
  10. broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster  


但是ExtensionLoader在实例化对象时,有个比较特殊的地方,那就是在实例化完成之后,会自动套上当前的ExtensionLoader中的Wrapper类,上面的mock所对应的MockClusterWrapper就是这样的一个Wrapper:

 

[java] view plain copy

  1. private T wrapInstance(T instance) throws IllegalArgumentException, SecurityException, ...  
  2. {  
  3.     Set<Class<?>> wrapperClassesToUse = wrapperClasses;  
  4.     T wrapper=instance;  
  5.     if (CollectionUtils.isNotEmpty(wrapperClassesToUse))  
  6.     {  
  7.         for (Class<?> wrapperClassToUse : wrapperClassesToUse)   
  8.         {  
  9.         wrapper=(T) wrapperClassToUse.getConstructor(type).newInstance(wrapper);  
  10.         wrapper = injectDependentExtension(wrapper);  
  11.         }  
  12.     }  
  13.     return wrapper;  
  14. }  


 

也就是实例化出来的FailoverCluster会被套上一层MockClusterWrapper,总结一下就是:

Cluster$Adaptive -> 定位到内部key为failover的对象 ->FailoverCluster->外部套上MockClusterWrapper ,

这样RegistryProctocol的doRefer方法中的:

[java] view plain copy

  1. return cluster.createClusterInvoker(registryDirectory);  


实际上调用的是MockClusterWrapper 的 createClusterInvoker方法,MockClusterWrapper 的 createClusterInvoker方法如下:

[java] view plain copy

  1. public class MockClusterWrapper implements Cluster   
  2. {  
  3.     private Cluster cluster;  
  4.     public MockClusterWrapper(Cluster cluster)  
  5.     {  
  6.          this.cluster = cluster;  
  7.     }  
  8.      @Override  
  9.      public <T> Invoker<T> createClusterInvoker(Directory<T> directory) throws RpcException  
  10.      {  
  11.       return new MockClusterInvoker<T>(directory,  
  12.         this.cluster.createClusterInvoker(directory));  
  13.      }  
  14. }  


 

也就是实际创建的ClusterInvoker是封装了FailoverClusterInvoker的MockClusterInvoker,这样就成功地在Invoker之中植入了Mock机制。

  那么,最终就是服务使用者的接口代理-> MockClusterInvoker -> FailoverClusterInvoker。

 

2. Mock的执行

   mock的执行主要由MockClusterInvoker完成,MockClusterInvoker的invoke方法的执行逻辑如下:

   (1)如果在没有配置之中没有设置mock,那么直接把方法调用转发给实际的Invoker(也就是FailoverClusterInvoker)

 

[java] view plain copy

  1. @Override  
  2. public Result invoke(Invocation invocation) throws RpcException  
  3. {  
  4. ult result = null;  
  5.       
  6.     String mockValue = directory.getUrl().getMethodParameter(  
  7.         invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();   
  8.     if (mockValue.length() == 0 || mockValue.equalsIgnoreCase("false"))  
  9.     {  
  10.         //no mock  
  11.         result = this.invoker.invoke(invocation);  
  12.     }  


 (2)如果配置了强制执行Mock,比如发生服务降级,那么直接按照配置执行mock之后返回:

[java] view plain copy

  1. else if (mockValue.startsWith("force"))  
  2. {  
  3.       if (logger.isWarnEnabled())  
  4.       {  
  5.          logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url: " +  directory.getUrl());  
  6.        }  
  7.       //force:direct mock  
  8.        result = doMockInvoke(invocation, null);  
  9. }  


(3) 如果是其它的情况,比如只是配置的是mock=fail:return null,那么就是在正常的调用出现异常的时候按照配置执行mock:

[java] view plain copy

  1. //fail-mock  
  2.  try   
  3.  {  
  4.     result = this.invoker.invoke(invocation);  
  5.  }  
  6.  catch (RpcException rpcException)   
  7.  {  
  8.      if (rpcException.isBiz())  
  9.      {  
  10.     throw rpcException;  
  11.      }   
  12.      else  
  13.      {  
  14.     if (logger.isWarnEnabled())  
  15.     {  
  16.     logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : "   
  17.      +  directory.getUrl(), rpcException);  
  18.         }  
  19.     result = doMockInvoke(invocation, rpcException);  
  20.      }  
  21.  }  


(2)和(3)最终都会通过调用doMockInvoke来完成mock调用,doMockInvoke方法会首先尝试调用selectMockInvoker方法来看看用户有没有配置过MockInvoker:

[java] view plain copy

  1. private Result doMockInvoke(Invocation invocation,RpcException actualRpcException)  
  2. {  
  3.         List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);  

 

 selectMockInvoker的代码如下,依靠在Invocation的attachment里面做个标记来告诉directory的list方法应该返回MockInvoker,代码的注释里面说这么做是临时之举,未来会修改:

 

[java] view plain copy

  1. private List<Invoker<T>> selectMockInvoker(Invocation invocation)  
  2. {  
  3.     //TODO generic invoker?  
  4.    if (invocation instanceof RpcInvocation)  
  5.    {  
  6.          //存在隐含契约(虽然在接口声明中增加描述,但扩展性会存在问题.同时放在attachement中的做法需要改进  
  7.       ((RpcInvocation)invocation).setAttachment(Constants.INVOCATION_NEED_MOCK, Boolean.TRUE.         toString());  
  8.       //directory根据invocation中attachment是否有Constants.INVOCATION_NEED_MOCK,来判断获取的是nor       //mal invokers or mock invokers  
  9.        List<Invoker<T>> invokers = directory.list(invocation);  
  10.        return invokers;  
  11.    }  
  12.    else   
  13.    {  
  14.        return null ;  
  15.    }  
  16. }  

一般情况下,directory是RegistryDirectory,RegistryDirectory的list方法里面与mock有关的部分主要是router,RegistryDirectory在初始化内部的routers的时候,会人为的加上一个MockInvokerRouter,

AbstractDirectory的setRouters方法:

 

[java] view plain copy

  1. protected void setRouters(List<Router> routers)  
  2. {  
  3.      // copy list  
  4.      routers = routers == null ? new  ArrayList<Router>() : new ArrayList<Router>(routers);  
  5.      // append url router  
  6.      String routerValue = url.getParameter(Constants.ROUTER_KEY);  
  7.      if (routerValue != null && routerValue.length() > 0)  
  8.       {  
  9.           RouterFactory routerFactory =   
  10.             ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerValue);  
  11.           routers.add(routerFactory.getRouter(url));  
  12.       }  
  13.      // append mock invoker selector  
  14.       routers.add(new MockInvokersRouter());  
  15.       Collections.sort(routers);  
  16.       this.routers = routers;  
  17.  }  

 

RegistryDirectory的list方法最后由router来对invokers进行处理:

AbstractDirectory的list方法:

[java] view plain copy

  1. public List<Invoker<T>> list(Invocation invocation) throws RpcException  
  2. {  
  3.      if (destroyed)  
  4.      {  
  5.           throw new RpcException("Directory already destroyed .url: "+ getUrl());  
  6.      }  
  7.      List<Invoker<T>> invokers = doList(invocation);  
  8.      List<Router> routersToUse = this.routers; // local reference  
  9.      if (routersToUse != null && routersToUse.size() > 0)   
  10.      {  
  11.          for (Router router: routersToUse)  
  12.          {  
  13.             try  
  14.             {  
  15.                  if (router.getUrl() == null || router.getUrl().getParameter(  
  16.                      Constants.RUNTIME_KEY, true))  
  17.                  {  
  18.                        invokers = router.route(invokers, getConsumerUrl(), invocation);  
  19.                    }  
  20.              }   
  21.              catch (Throwable t)  
  22.              {  
  23.                   logger.error("Failed to execute router: " + getUrl() + ", cause: " +  
  24.                      t.getMessage(), t);  
  25.               }  
  26.           }  
  27.       }  
  28.       return invokers;  
  29.     }  

MockInvokersRouter的route方法如下,根据Invocation的的attachment里面是否有mock标记(这个标记在前述的MockClusterInvoker的selectMockInvoker方法里面设置)来决定是返回正常的Invoker还是MockInvoker:

 

[java] view plain copy

  1. public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,URL url, final Invocation invocation) throws RpcException  
  2. {  
  3.     if (invocation.getAttachments() == null)  
  4.     {  
  5.     return getNormalInvokers(invokers);  
  6.     }   
  7.     else  
  8.     {  
  9.     String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);  
  10.     if (value == null)   
  11.     {  
  12.         return getNormalInvokers(invokers);  
  13.     }  
  14.     else if (Boolean.TRUE.toString().equalsIgnoreCase(value))  
  15.     {  
  16.         return getMockInvokers(invokers);  
  17.     }   
  18.     }  
  19.     return invokers;  
  20. }  

负责返回MockInvoker的是下面的getMockInvokers方法,在一般的情况下,是不会配置mock协议的,所以这个方法返回null:

 

[java] view plain copy

  1. private <T> List<Invoker<T>> getMockInvokers(final List<Invoker<T>> invokers)  
  2. {  
  3.      if (! hasMockProviders(invokers))  
  4.      {  
  5.     return null;  
  6.      }  
  7.      List<Invoker<T>> resultInvokers = new ArrayList<Invoker<T>>(1);  
  8.      for (Invoker<T> invoker : invokers)  
  9.      {  
  10.     if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL))  
  11.     {  
  12.         resultInvokers.add(invoker);  
  13.     }  
  14.      }  
  15.      return resultInvokers;  
  16. }  


这样一直返回到MockClusterInvoker的doMockInvoke方法之中,selectMockInvoker返回空,那么MockClusterInvoker的doMockInvoke方法会根据url来

构造一个MockInvoker:

[java] view plain copy

  1. private Result doMockInvoke(Invocation invocation,RpcException actualRpcException)  
  2. {  
  3.     List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);  
  4.     Invoker<T> mockInvokerToUse ;  
  5.     if (mockInvokers == null || mockInvokers.size() == 0)  
  6.     {  
  7.     mockInvokerToUse = (Invoker<T>) new MockInvoker(directory.getUrl());  
  8.     }   
  9.     else   
  10.     {  
  11.     mockInvokerToUse = mockInvokers.get(0);  
  12.     }  
  13.           
  14.     Result result = null;  
  15.     try   
  16.     {  
  17.     result = mockInvokerToUse.invoke(invocation);  
  18.     }   
  19.     catch (RpcException mockRpcException)  
  20.     {  

最后在构造出来的MockInvoker上调用invoke方法来执行mock调用,invoke方法的流程比较简单,对mockValue进行处理之后就看是返回mock值还是抛出异常,或者是加载并调用Mock类:

 

[java] view plain copy

  1. public Result invoke(Invocation invocation) throws RpcException   
  2. {  
  3.         String mockValue =   
  4.             getUrl().getParameter(invocation.getMethodName()+"."+Constants.MOCK_KEY);  
  5.         if (invocation instanceof RpcInvocation)   
  6.         {  
  7.             ((RpcInvocation) invocation).setInvoker(this);  
  8.         }  
  9.         if (StringUtils.isBlank(mockValue))  
  10.         {  
  11.             mockValue = getUrl().getParameter(Constants.MOCK_KEY);  
  12.         }  
  13.           
  14.         if (StringUtils.isBlank(mockValue))  
  15.         {  
  16.             throw new RpcException(  
  17.                new IllegalAccessException("mock can not be null. url :" + url));  
  18.         }  
  19.         mockValue = normalizeMock(URL.decode(mockValue));  
  20.         if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mockValue.trim()))  
  21.         {  
  22.             RpcResult result = new RpcResult();  
  23.             result.setValue(null);  
  24.             return result;  
  25.         }  
  26.           
  27.         if (mockValue.startsWith(Constants.RETURN_PREFIX))   
  28.         {  
  29.            ....  
  30.         }   
  31.           
  32.         if (mockValue.startsWith(Constants.THROW_PREFIX))   
  33.         {  
  34.            ....  
  35.         }  
  36.          //impl mock  
  37.         try  
  38.         {  
  39.            Invoker<T> invoker = getInvoker(mockValue);  
  40.            return invoker.invoke(invocation);  
  41.         }  
  42.         catch (Throwable t)   
  43.         {  
  44.           throw new RpcException("Failed to create mock implemention class " + mockValue , t);  
  45.         }  
  46.   
  47.     }  



最后欢迎大家访问我的个人网站:1024s

 



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值