Mock是SOA之中的一个很有用的功能,不仅可以用来进行服务降级,也可以用来在测试中模拟服务调用的各种异常情况。dubbo框架里面的mock是在服务使用者这一端实现的,下面对实现机制进行分析:
1. Mock的植入
很显然,既然提供了mock机制,那么mock应该作为一个环节插入到服务使用者的整个处理流程之中,而dubbo的设计基本采用了装饰器模式,一层一层的进行包装,这个具体的植入点就在RegistryProctocol通过Cluster来创建ClusterInvoker的时候:
RegistryProctocol的doRefer方法:
[java] view plain copy
- 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
- @SPI(FailoverCluster.NAME)
- public interface Cluster
- {
在dubbo的配置文件 classpath:/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.Cluster中,failover对应的是FailoverCluster类:
[java] view plain copy
- mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
- failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster
- failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster
- failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster
- failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster
- forking=com.alibaba.dubbo.rpc.cluster.support.ForkingCluster
- available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster
- switch=com.alibaba.dubbo.rpc.cluster.support.SwitchCluster
- mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster
- broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster
但是ExtensionLoader在实例化对象时,有个比较特殊的地方,那就是在实例化完成之后,会自动套上当前的ExtensionLoader中的Wrapper类,上面的mock所对应的MockClusterWrapper就是这样的一个Wrapper:
[java] view plain copy
- private T wrapInstance(T instance) throws IllegalArgumentException, SecurityException, ...
- {
- Set<Class<?>> wrapperClassesToUse = wrapperClasses;
- T wrapper=instance;
- if (CollectionUtils.isNotEmpty(wrapperClassesToUse))
- {
- for (Class<?> wrapperClassToUse : wrapperClassesToUse)
- {
- wrapper=(T) wrapperClassToUse.getConstructor(type).newInstance(wrapper);
- wrapper = injectDependentExtension(wrapper);
- }
- }
- return wrapper;
- }
也就是实例化出来的FailoverCluster会被套上一层MockClusterWrapper,总结一下就是:
Cluster$Adaptive -> 定位到内部key为failover的对象 ->FailoverCluster->外部套上MockClusterWrapper ,
这样RegistryProctocol的doRefer方法中的:
[java] view plain copy
- return cluster.createClusterInvoker(registryDirectory);
实际上调用的是MockClusterWrapper 的 createClusterInvoker方法,MockClusterWrapper 的 createClusterInvoker方法如下:
[java] view plain copy
- public class MockClusterWrapper implements Cluster
- {
- private Cluster cluster;
- public MockClusterWrapper(Cluster cluster)
- {
- this.cluster = cluster;
- }
- @Override
- public <T> Invoker<T> createClusterInvoker(Directory<T> directory) throws RpcException
- {
- return new MockClusterInvoker<T>(directory,
- this.cluster.createClusterInvoker(directory));
- }
- }
也就是实际创建的ClusterInvoker是封装了FailoverClusterInvoker的MockClusterInvoker,这样就成功地在Invoker之中植入了Mock机制。
那么,最终就是服务使用者的接口代理-> MockClusterInvoker -> FailoverClusterInvoker。
2. Mock的执行
mock的执行主要由MockClusterInvoker完成,MockClusterInvoker的invoke方法的执行逻辑如下:
(1)如果在没有配置之中没有设置mock,那么直接把方法调用转发给实际的Invoker(也就是FailoverClusterInvoker)
[java] view plain copy
- @Override
- public Result invoke(Invocation invocation) throws RpcException
- {
- ult result = null;
- String mockValue = directory.getUrl().getMethodParameter(
- invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
- if (mockValue.length() == 0 || mockValue.equalsIgnoreCase("false"))
- {
- //no mock
- result = this.invoker.invoke(invocation);
- }
(2)如果配置了强制执行Mock,比如发生服务降级,那么直接按照配置执行mock之后返回:
[java] view plain copy
- else if (mockValue.startsWith("force"))
- {
- if (logger.isWarnEnabled())
- {
- logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url: " + directory.getUrl());
- }
- //force:direct mock
- result = doMockInvoke(invocation, null);
- }
(3) 如果是其它的情况,比如只是配置的是mock=fail:return null,那么就是在正常的调用出现异常的时候按照配置执行mock:
[java] view plain copy
- //fail-mock
- try
- {
- result = this.invoker.invoke(invocation);
- }
- catch (RpcException rpcException)
- {
- if (rpcException.isBiz())
- {
- throw rpcException;
- }
- else
- {
- if (logger.isWarnEnabled())
- {
- logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : "
- + directory.getUrl(), rpcException);
- }
- result = doMockInvoke(invocation, rpcException);
- }
- }
(2)和(3)最终都会通过调用doMockInvoke来完成mock调用,doMockInvoke方法会首先尝试调用selectMockInvoker方法来看看用户有没有配置过MockInvoker:
[java] view plain copy
- private Result doMockInvoke(Invocation invocation,RpcException actualRpcException)
- {
- List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
selectMockInvoker的代码如下,依靠在Invocation的attachment里面做个标记来告诉directory的list方法应该返回MockInvoker,代码的注释里面说这么做是临时之举,未来会修改:
[java] view plain copy
- 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,来判断获取的是nor //mal invokers or mock invokers
- List<Invoker<T>> invokers = directory.list(invocation);
- return invokers;
- }
- else
- {
- return null ;
- }
- }
一般情况下,directory是RegistryDirectory,RegistryDirectory的list方法里面与mock有关的部分主要是router,RegistryDirectory在初始化内部的routers的时候,会人为的加上一个MockInvokerRouter,
AbstractDirectory的setRouters方法:
[java] view plain copy
- protected void setRouters(List<Router> routers)
- {
- // copy list
- routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
- // append url router
- String routerValue = url.getParameter(Constants.ROUTER_KEY);
- if (routerValue != null && routerValue.length() > 0)
- {
- RouterFactory routerFactory =
- ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerValue);
- routers.add(routerFactory.getRouter(url));
- }
- // append mock invoker selector
- routers.add(new MockInvokersRouter());
- Collections.sort(routers);
- this.routers = routers;
- }
RegistryDirectory的list方法最后由router来对invokers进行处理:
AbstractDirectory的list方法:
[java] view plain copy
- public List<Invoker<T>> list(Invocation invocation) throws RpcException
- {
- if (destroyed)
- {
- throw new RpcException("Directory already destroyed .url: "+ getUrl());
- }
- List<Invoker<T>> invokers = doList(invocation);
- List<Router> routersToUse = this.routers; // local reference
- if (routersToUse != null && routersToUse.size() > 0)
- {
- for (Router router: routersToUse)
- {
- try
- {
- if (router.getUrl() == null || router.getUrl().getParameter(
- Constants.RUNTIME_KEY, true))
- {
- invokers = router.route(invokers, getConsumerUrl(), invocation);
- }
- }
- catch (Throwable t)
- {
- logger.error("Failed to execute router: " + getUrl() + ", cause: " +
- t.getMessage(), t);
- }
- }
- }
- return invokers;
- }
MockInvokersRouter的route方法如下,根据Invocation的的attachment里面是否有mock标记(这个标记在前述的MockClusterInvoker的selectMockInvoker方法里面设置)来决定是返回正常的Invoker还是MockInvoker:
[java] view plain copy
- public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,URL url, final Invocation invocation) throws RpcException
- {
- if (invocation.getAttachments() == null)
- {
- return getNormalInvokers(invokers);
- }
- else
- {
- String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
- if (value == null)
- {
- return getNormalInvokers(invokers);
- }
- else if (Boolean.TRUE.toString().equalsIgnoreCase(value))
- {
- return getMockInvokers(invokers);
- }
- }
- return invokers;
- }
负责返回MockInvoker的是下面的getMockInvokers方法,在一般的情况下,是不会配置mock协议的,所以这个方法返回null:
[java] view plain copy
- private <T> List<Invoker<T>> getMockInvokers(final List<Invoker<T>> invokers)
- {
- if (! hasMockProviders(invokers))
- {
- return null;
- }
- List<Invoker<T>> resultInvokers = new ArrayList<Invoker<T>>(1);
- for (Invoker<T> invoker : invokers)
- {
- if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL))
- {
- resultInvokers.add(invoker);
- }
- }
- return resultInvokers;
- }
这样一直返回到MockClusterInvoker的doMockInvoke方法之中,selectMockInvoker返回空,那么MockClusterInvoker的doMockInvoke方法会根据url来
构造一个MockInvoker:
[java] view plain copy
- private Result doMockInvoke(Invocation invocation,RpcException actualRpcException)
- {
- List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
- Invoker<T> mockInvokerToUse ;
- if (mockInvokers == null || mockInvokers.size() == 0)
- {
- mockInvokerToUse = (Invoker<T>) new MockInvoker(directory.getUrl());
- }
- else
- {
- mockInvokerToUse = mockInvokers.get(0);
- }
- Result result = null;
- try
- {
- result = mockInvokerToUse.invoke(invocation);
- }
- catch (RpcException mockRpcException)
- {
最后在构造出来的MockInvoker上调用invoke方法来执行mock调用,invoke方法的流程比较简单,对mockValue进行处理之后就看是返回mock值还是抛出异常,或者是加载并调用Mock类:
[java] view plain copy
- public Result invoke(Invocation invocation) throws RpcException
- {
- String mockValue =
- getUrl().getParameter(invocation.getMethodName()+"."+Constants.MOCK_KEY);
- if (invocation instanceof RpcInvocation)
- {
- ((RpcInvocation) invocation).setInvoker(this);
- }
- if (StringUtils.isBlank(mockValue))
- {
- mockValue = getUrl().getParameter(Constants.MOCK_KEY);
- }
- if (StringUtils.isBlank(mockValue))
- {
- throw new RpcException(
- new IllegalAccessException("mock can not be null. url :" + url));
- }
- mockValue = normalizeMock(URL.decode(mockValue));
- if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mockValue.trim()))
- {
- RpcResult result = new RpcResult();
- result.setValue(null);
- return result;
- }
- if (mockValue.startsWith(Constants.RETURN_PREFIX))
- {
- ....
- }
- if (mockValue.startsWith(Constants.THROW_PREFIX))
- {
- ....
- }
- //impl mock
- try
- {
- Invoker<T> invoker = getInvoker(mockValue);
- return invoker.invoke(invocation);
- }
- catch (Throwable t)
- {
- throw new RpcException("Failed to create mock implemention class " + mockValue , t);
- }
- }
最后欢迎大家访问我的个人网站:1024s