跟著小程學微服務-Mock自動化系統的原理及實現

colincheng發表於2017-06-01

一、前言

在之前的文章 http://www.jianshu.com/p/c128ed5c394e 中已經介紹了“自動化Mock系統0.9版本”,今天我將和大家一起探討我們的“自動化Mock系統1.0版本”。

二、測試人員面臨的測試問題

我公司目前用的是基於Dubbo的微服務改造,服務之間的呼叫鏈路冗長,每個服務又是單獨的團隊在維護,每個團隊又在不斷的演進和維護各個服務,那麼對測試人員將是非常大的挑戰。

測試人員每次進行功能測試的時候,測試用例每次都需要重新寫一遍,無法將測試用例的資料沉澱,尤其是做自動化測試的時候,測試人員準備測試資料就需要很長時間,效率非常低。

目前介面自動化測試框架也多種多樣,testng,junit,Fitnesse等,但都需要測試人員具備測試程式碼編寫能力,如果要做好和手工介面測試一樣效果的自動化測試更是需要大量的程式碼堆積,後期維護程式碼成本非常大。因此做成簡單配置用例流,無需編寫測試程式碼的系統是更貼合實際工作要求。

舉個例子:拿網際網路支付系統來說,某個團隊新增了支付交易的需求,這時候要進行測試,測試人員除了要測試支付交易需求本身是否正確,同時也要結合上下游的服務整體進行迴歸測試,這時候開發人員往往在支付交易系統中採用“硬編碼”的方式對上下游的系統進行“擋板”,如果測試人員對測試資料有所調整那麼“擋板”也要跟著調整,同時在專案正式上線的時候,如果開發人員沒有將“擋板”程式去除乾淨,將面臨嚴重的線上問題。

三、Dubbo的Mock功能

1、Dubbo的Mock使用

Dubbo自帶的Mock功能首先是為了做服務降級,比如某驗權服務,當服務提供方全部掛掉後,客戶端不丟擲異常,而是通過Mock資料返回授權失敗。

我們從官網上舉一個例子來說明:

<dubbo:reference interface="com.foo.BarService" mock="force" />

我們可以在期望的reference標籤上加一個mock=”force”,就可以將當前服務設定為mock。但是設定完mock屬性後還沒有結束,需要有一個Mock類對應我們的服務介面類。

規則如下:
介面名 + Mock字尾,服務介面呼叫失敗Mock實現類,該Mock類必須有一個無參建構函式。

對應到com.foo.BarService的話,則建立BarServiceMock類。

public class BarServiceMock implements BarService {
 
    public String sayHello(String name) {
        // 你可以偽造容錯資料,此方法只在出現RpcException時被執行
        return "容錯資料";
    }
}

經過以上設定後,當呼叫BarService進行遠端呼叫的話,直接請求到BarServiceMock類上面進行模擬測試。

2、Dubbo Mock的原理解析

在dubbo的配置檔案中
classpath:/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.Cluster
可以看到如下配置列表:

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

我們可以看到配置檔案中實際上有五大路由策略:

  • AvailableCluster: 獲取可用的呼叫。遍歷所有Invokers判斷Invoker.isAvalible,只要一個有為true直接呼叫返回,不管成不成功。

  • BroadcastCluster: 廣播呼叫。遍歷所有Invokers, 逐個呼叫每個呼叫catch住異常不影響其他invoker呼叫。

  • FailbackCluster: 失敗自動恢復, 對於invoker呼叫失敗, 後臺記錄失敗請求,任務定時重發, 通常用於通知。

  • FailfastCluster: 快速失敗,只發起一次呼叫,失敗立即保錯,通常用於非冪等性操作。

  • FailoverCluster: 失敗轉移,當出現失敗,重試其它伺服器,通常用於讀操作,但重試會帶來更長延遲。

Dubbo中預設使用的是FailoverCluster策略,而在實際執行的過程中是FailoverCluster會被先被注入到MockClusterWrapper中,過程就是:

Cluster$Adaptive -> 定位到內部key為failover的物件 ->FailoverCluster->注入到MockClusterWrapper 

MockClusterWrapper內部會建立一個MockClusterInvoker物件。實際建立是封裝了FailoverClusterInvoker的MockClusterInvoker,這樣就成功地在Invoker之中植入了Mock機制。

我們來看MockClusterInvoker的內部實現:

  • 如果在沒有配置之中沒有設定mock,那麼直接把方法呼叫轉發給實際的Invoker(也就是FailoverClusterInvoker)。
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);  
    }  
  • 如果配置了強制執行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);  
}  
  • 如果是其它的情況,比如只是配置的是mock=fail:return null,那麼就是在正常的呼叫出現異常的時候按照配置執行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);  
     }  
 }  
3、Dubbo Mock的適用場景

Dubbo的Mock功能主要是為了做服務降級而使用的,服務提供方在客戶端執行容錯邏輯,在出現RpcException(比如網路失敗,超時等)時進行容錯,然後執行降級Mock邏輯。自身並不適合做Mock測試系統。

四、自動化Mock系統的實現

1、Mock系統的簡單用例圖
image.png
2、Mock系統的架構圖
image.png

為了基於Dubbo實現Mock功能,需要對Dubbo原始碼進行一些必要的修改,通過上面的架構圖我們可以看到,實際上我們正是利用了Dubbo的Filter chain過濾器鏈這一機制實現的,為了方便大家更好的理解,下面將簡單介紹一下Dubbo的Filter機制。

2.1、Dubbo的Filter原理分析

Filter:是一種遞迴的鏈式呼叫,用來在遠端呼叫真正執行的前後加入一些邏輯,跟aop的攔截器servlet中filter概念一樣的。

Filter介面定義:

@SPI

public interface Filter {

   Result invoke(Invoker<?> invoker,Invocation invocation) throws RpcException;

}

Filter的實現類需要打上@Activate註解, @Activate的group屬性是個string陣列,我們可以通過這個屬性來指定這個filter是在consumer, provider還是兩者情況下啟用,所謂啟用就是能夠被獲取,組成filter鏈。

List<Filter> filters =ExtensionLoader.getExtensionLoader(Filter.class).getAct ivateExtension(invoker.getUrl(),key, group);

Key就是SERVICE_FILTER_KEY還是REFERENCE_FILTER_KEY

Group就是consumer或者provider

關於SPI的詳細介紹請大家參考我之前寫的另一篇文章http://www.jianshu.com/p/46aa69643c97

ProtocolFilterWrapper:在服務的暴露與引用的過程中根據KEY是PROVIDER還是CONSUMER來構建服務提供者與消費者的呼叫過濾器鏈,Filter最終都要被封裝到Wrapper中的。

public <T> Exporter<T> export(Invoker<T>invoker)throws RpcException {

return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}

public <T> Invoker<T> refer(Class<T> type,URL url)throws RpcException {

     return buildInvokerChain(protocol.refer(type, url),Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}

構建filter鏈,當我們獲取啟用的filter集合後就通過ProtocolFilterWrapper類中的buildInvokerChain方法來構建。

for (int i = filters.size() - 1; i >= 0; i --) {
      final Filter filter = filters.get(i);
      final Invoker<T> next = last;
      last = new Invoker<T>() {
            public Result invoke(Invocation invocation)throws RpcException {
                 return filter.invoke(next, invocation);
            }
           。。。。。。。 //其他方法
       };
 }
2.2、Mock流程介紹
image.png

注:我們在<dubbo:application name>中新加了自定義的“env=test”這樣的屬性配置用來標明當前環境是測試的還是正式的,使用者每次通過Dubbo請求的遠端服務的時候,都會首先經過我們自定義的Filter,我們自定義的Filter會首先判斷當前的環境是test還是正式,如果是test的環境則直接訪問Mock配置中心獲取提前配置好的Mock資料並封裝成使用者定義的Response物件返回。

3、Mock系統的配置中心

Mock配置中心就是使用者將mock資料與應用環境建立關係的系統,整個系統就像一個工作流引擎:

環境設定->應用名稱設定->擋板規則設定->Facade服務介面設定->方法規則設定

  • 環境設定
image.png

注:如果尚未對映來源IP地址到環境,則點選環境列表導航連結,進入環境列表頁面,點選新增,輸入源IP及環境名,點選確定按鈕,實現源IP到所設環境的對映。每個使用者都可以建立屬於自己的測試環境。

  • 應用名稱設定
image.png

注:建立所使用系統的應用名稱,Mock配置中心預設使用<dubbo:application name>中的名稱作為應用名稱。

  • 擋板規則
image.png

注:每一個擋板規則都是由一個環境名稱和應用名稱組成的唯一擋板,在擋板設定中選擇環境名稱和應用名稱,並且設定擋板的有效狀態。

  • Facade規則
image.png

注:每一個Facade就是一個Dubbo的服務介面類,在這裡將自己的Facade名稱與全路徑與擋板名稱對應,以標識哪些Facade服務介面類是屬於哪個擋板的。

  • 方法規則
image.png

注:方法規則是用來設定每個Facade中的需要mock的方法的,可以對不同的方法設定方法執行時間、方法丟擲的異常等等。

4、Mock系統的其他功能

由於不少應用專案開發完後想對其進行單獨壓測,而很多時候應用系統和其他業務系統形成了依賴關係,如果不佈署其他應用系統則無法完成壓測,為了更好的支援效能測試組進行擋板壓測,Mock系統支援壓測功能,而Mock系統自身也可以達到單臺伺服器1000TPS以上(8C8G)。


相關文章