dubbo中的Mock實現機制

Mock是SOA之中的一個頗有用的功能,不只能夠用來進行服務降級,也能夠用來在測試中模擬服務調用的各類異常狀況。dubbo框架裏面的mock是在服務使用者這一端實現的,下面對實現機制進行分析:java

  1. 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);
        }

    }
相關文章
相關標籤/搜索