Mock是SOA之中的一个颇有用的功能,不只能够用来进行服务降级,也能够用来在测试中模拟服务调用的各类异常状况。dubbo框架里面的mock是在服务使用者这一端实现的,下面对实现机制进行分析:java
Mock的植入app
很显然,既然提供了mock机制,那么mock应该做为一个环节插入到服务使用者的整个处理流程之中,而dubbo的设计基本采用了装饰器模式,一层一层的进行包装,这个具体的植入点就在RegistryProctocol经过Cluster来建立ClusterInvoker的时候:框架
RegistryProctocol的doRefer方法:ide
return cluster.createClusterInvoker(registryDirectory);
cluster的类型为Cluster$Adaptive,这其实是一个通用的代理类,它会根据regsitryDirectory的getConsumerUrl方法返回的Url中的cluster参数的值来定位到实际的Cluster的实现类上,若是Url之中没有指定cluster,那么会采用Cluster的SPI注解上配置的默认值FailoverCluster.NAME,也就是默认状况下会调用ExtensionLoader<Clsuter>内部的key为failover的实例:测试
@SPI(FailoverCluster.NAME) public interface Cluster {
在dubbo的配置文件 classpath:/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.Cluster中,failover对应的是FailoverCluster类。this
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就是这样的一个Wrapperurl
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,总结一下就是:spa
Cluster$Adaptive -> 定位到内部key为failover的对象 ->FailoverCluster->外部套上MockClusterWrapper ,设计
这样RegistryProctocol的doRefer方法中的:代理
return cluster.createClusterInvoker(registryDirectory);
实际上调用的是MockClusterWrapper 的 createClusterInvoker方法,MockClusterWrapper 的 createClusterInvoker方法以下:
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完成,invoke方法的执行逻辑以下:
(1)若是在没有配置之中没有设置mock,那么直接把方法调用转发给实际的Invoker(也就是FailoverClusterInvoker)
@Override public Result invoke(Invocation invocation) throws RpcException { Result 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以后返回:
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:
//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:
private Result doMockInvoke(Invocation invocation,RpcException actualRpcException) { List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
selectMockInvoker的代码以下,依靠在Invocation的attachment里面作个标记来告诉directory的list方法应该返回MockInvoker,代码的注释里面说这么作是临时之举,将来会修改:
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方法:
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方法:
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:
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,
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:
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类:
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); } }