歡迎光臨
每天分享高質量文章

Nepxion Discovery:Spring Cloud灰度釋出神器

Nepxion Discovery是一款對Spring Cloud服務註冊發現和負載均衡的增強中介軟體,其功能包括灰度釋出(包括切換髮布和平滑釋出),黑/白名單的IP地址過濾,限制註冊,限制發現等,支援Eureka、Consul和Zookeeper,支援Spring Cloud Api Gateway(Finchley版)、Zuul閘道器和微服務的灰度釋出,支援使用者自定義和程式設計灰度路由策略,支援多資料源的資料庫灰度釋出等客戶特色化灰度釋出,支援Nacos和Redis為遠端配置中心,同時相容Spring Cloud Edgware版和Finchley版。
現有Spring Cloud微服務痛點

  1. 如果你是運維負責人,是否會經常發現,你掌管的測試環境中的服務註冊中心,被一些不負責的開發人員把他本地開發環境註冊上來,造成測試人員測試失敗。你希望可以把本地開發環境註冊給遮蔽掉,不讓註冊。

  2. 如果你是運維負責人,生產環境的某個微服務叢集下的某個實體,暫時出了問題,但又不希望它下線。你希望可以把該實體給遮蔽掉,暫時不讓它被呼叫。

  3. 如果你是業務負責人,鑒於業務服務的快速迭代性,微服務叢集下的實體釋出不同的版本。你希望根據版本管理策略進行路由,提供給下游微服務區別呼叫,例如訪問控制快速基於版本的不同而切換,例如在不同的版本之間進行流量調撥。

  4. 如果你是業務負責人,希望灰度釋出功能可以基於業務場景特色定製,例如根據使用者手機號進行不同伺服器的路由。

  5. 如果你是DBA負責人,希望灰度釋出功能可以基於資料庫切換上。

  6. 如果你是測試負責人,希望對微服務做A/B測試,那麼透過動態改變版本達到該目的。


Spring Cloud微服務痛點場景表現

黑/白名單的IP地址註冊的過濾
開發環境的本地微服務(例如IP地址為172.16.0.8)不希望被註冊到測試環境的服務註冊發現中心,那麼可以在配置中心維護一個黑/白名單的IP地址過濾(支援全域性和區域性的過濾)的規則。
我們可以透過提供一份黑/白名單達到該效果。
最大註冊數的限制的過濾
當某個微服務註冊數目已經達到上限(例如10個),那麼後面起來的微服務,將再也不能註冊上去。
黑/白名單的IP地址發現的過濾
開發環境的本地微服務(例如IP地址為172.16.0.8)已經註冊到測試環境的服務註冊發現中心,那麼可以在配置中心維護一個黑/白名單的IP地址過濾(支援全域性和區域性的過濾)的規則,該本地微服務不會被其他測試環境的微服務所呼叫。
我們可以透過推送一份黑/白名單達到該效果。
多版本訪問的灰度控制
A服務呼叫B服務,而B服務有兩個實體(B1、B2),雖然三者有相同的服務名,但功能上有差異,需求是在某個時刻,A服務只能呼叫B1,禁止呼叫B2。在此場景下,我們在application.properties裡為B1維護一個版本為1.0,為B2維護一個版本為1.1。
我們可以透過推送A服務呼叫某個版本的B服務對應關係的配置,達到某種意義上的灰度控制,改變版本的時候,我們只需要再次推送即可。
多版本權重的灰度控制
上述場景中,我們也可以透過配對不同版本的權重(流量比例),根據需求,A訪問B的流量在B1和B2進行調撥。
多資料源的資料庫灰度控制
我們事先為微服務配置多套資料源,透過灰度釋出實時切換資料源。
動態改變微服務版本
在A/B測試中,透過動態改變版本,不重啟微服務,達到訪問版本的路徑改變。
使用者自定義和程式設計灰度路由策略,可以透過非常簡單程式設計達到如下效果:
  • 我們可以在閘道器上根據不同的Token查詢到不同的使用者,把請求路由到指定的伺服器。

  • 我們可以在服務上根據不同的業務引數,例如手機號或者身份證號,把請求路由到指定的伺服器。

Nepxion Discovery概念和功能介紹

Nepxion Discovery是一款對Spring Cloud服務註冊發現和負載均衡的增強中介軟體,其功能包括灰度釋出(包括切換髮布和平滑釋出),黑/白名單的IP地址過濾,限制註冊,限制發現等,支援Eureka、Consul和Zookeeper,支援Spring Cloud Api Gateway(Finchley版)、Zuul閘道器和微服務的灰度釋出,支援使用者自定義和程式設計灰度路由策略,支援Nacos和Redis為遠端配置中心,同時相容Spring Cloud Edgware版和Finchley版。
Nepxion Discovery包含功能:
實現對基於Spring Cloud的微服務和Spring Cloud Api Gateway(F版)和Zuul閘道器的支援:
  • 具有極大的靈活性——支援在任何環節做過濾控制和灰度釋出。

  • 具有極小的限制性——只要開啟了服務註冊發現,程式入口加了@EnableDiscoveryClient。

  • 具有極強的可用性——當遠端配置中心全部掛了,可以透過Rest方式進行灰度釋出。

實現服務註冊層面的控制:
  • 基於黑/白名單的IP地址過濾機制禁止對相應的微服務進行註冊。

  • 基於最大註冊數的限制微服務註冊。一旦微服務叢集下註冊的實體數目已經達到上限,將禁止後續的微服務進行註冊。

實現服務發現層面的控制:
  • 基於黑/白名單的IP地址過濾機制禁止對相應的微服務被髮現。

  • 基於版本號配對,透過對消費端和提供端可訪問版本對應關係的配置,在服務發現和負載均衡層面,進行多版本訪問控制。

  • 基於版本權重配對,透過對消費端和提供端版本權重(流量)對應關係的配置,在服務發現和負載均衡層面,進行多版本流量調撥訪問控制。

實現灰度釋出:
  • 透過版本的動態改變,實現切換灰度釋出。

  • 透過版本訪問規則的改變,實現切換灰度釋出。

  • 透過版本權重規則的改變,實現平滑灰度釋出。

實現透過XML或者Json進行上述規則的定義。
實現透過事件匯流排機制(EventBus)的功能,實現釋出/訂閱功能:
  • 對接遠端配置中心,整合Nacos和Redis,非同步接受遠端配置中心主動推送規則資訊,動態改變微服務的規則。

  • 結合Spring Boot Actuator,非同步接受Rest主動推送規則資訊,動態改變微服務的規則,支援同步和非同步推送兩種方式。

  • 結合Spring Boot Actuator,動態改變微服務的版本,支援同步和非同步推送兩種方式。

  • 在服務註冊層面的控制中,一旦禁止註冊的條件觸發,主動推送非同步事件,以便使用者訂閱。

實現透過Listener機制進行擴充套件:
  • 使用者可以對服務註冊發現核心事件進行監聽,實現透過擴充套件,使用者自定義和程式設計灰度路由策略。

實現透過擴充套件,使用者自定義和程式設計灰度路由策略:
  • 使用者可以實現跟業務有關的路由策略,根據業務引數的不同,負載均衡到不同的伺服器。

  • 使用者可以根據內建的版本路由策略+自定義策略,隨心所欲的達到需要的路由功能。

實現支援Spring Boot Admin的整合。
實現支援未來擴充套件更多的服務註冊中心。
實現控制平臺微服務,支援對規則和版本集中管理、推送、更改和刪除。
實現基於控制平臺微服務的圖形化的灰度釋出功能。
上述文中提到切換灰度釋出和平滑灰度釋出,切換灰度釋出即在灰度釋出的時候,沒有過渡過程,流量直接從舊版本切換到新版本;平滑灰度釋出即在灰度釋出的時候,有個過渡過程,可以根據實際情況,先給新版本分配低額流量,給舊版本分配高額流量,對新版本進行監測,如果沒有問題,就繼續把舊版的流量切換到新版本上。
Nepxion Discovery規則和策略介紹

現有Spring Cloud微服務可以方便引入Nepxion Discovery,程式碼零侵入,使用者只需要做如下簡單的事情:
引入相關依賴到pom.xml。
  • 在application.properties或者application.yml中,必須為微服務定義一個版本號(version),必須為微服務自定義一個組名(group)或者應用名(application)。

  • 使用者只需要關註相關規則推送。可以採用如下方式之一:

  1. 透過遠端配置中心推送規則

  2. 透過控制檯介面推送規則

  3. 透過客戶端工具(例如Postman)推送

規則示例解釋:
基於黑/白名單的IP地址過濾機制禁止對相應的微服務進行註冊。
<register>
    <blacklist>
        <service service-name="a" filter-value="172.16"/>
    blacklist>


    <whitelist>
        <service service-name=“a” filter-value=“10.10”/>
    whitelist>
register>

黑名單,表示a服務註冊到服務註冊發現中心,如果它的IP起始為172.16,那麼禁止註冊。
白名單,表示a服務註冊到服務註冊發現中心,如果它的IP起始為10.10,那麼允許註冊。
基於最大註冊數的限制微服務註冊。
<register>
    <count>
        <service service-name="a" filter-value="500"/>
    count>


register>

表示a服務註冊到服務註冊發現中心,當達到500個後,更多的實體將無法註冊。
基於黑/白名單的IP地址過濾機制禁止對相應的微服務被髮現。
<discovery>
    <blacklist>
        <service service-name="a" filter-value="172.16"/>
    blacklist>


    <whitelist>
        <service service-name=“a” filter-value=“10.10”/>
    whitelist>
discovery>

黑名單,表示a服務的IP起始為172.16,那麼禁止被髮現。
白名單,表示a服務的IP起始為10.10,那麼允許被髮現。
服務發現的多版本灰度訪問控制。
<discovery>
    <version>
        <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0" provider-version-value="1.0"/>
        <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.1" provider-version-value="1.1"/>        
    version>


discovery>

表示消費端服務a的1.0,允許訪問提供端服務b的1.0版本。
表示消費端服務a的1.1,允許訪問提供端服務b的1.1版本。
服務發現的多版本權重灰度訪問控制。
<discovery>
    <weight>
        <service consumer-service-name="a" provider-service-name="b" provider-weight-value="1.0=90;1.1=10"/>
    weight>


discovery>

表示消費端服務a訪問提供端服務b的時候,提供端服務b的1.0版本提供90%的權重流量,1.1版本提供10%的權重流量。
簡單的多資料源資料庫灰度切換。
<customization>
    <service service-name="discovery-springcloud-example-b" key="database" value="prod"/>
customization>


表示服務b有兩個庫的配置,分別是臨時資料庫(database的value為temp)和生產資料庫(database的value為prod)。
上線後,一開始資料庫指向臨時資料庫,對應value為temp,然後灰度釋出的時候,改對應value為prod,即實現資料庫的灰度釋出。
全域性架構圖:
模組結構圖:
兩個基於閘道器的灰度釋出示例

兩個基於閘道器的灰度釋出示例,您可以研究更多的灰度釋出策略。
基於閘道器版本權重的灰度釋出
灰度釋出前:
  1. 閘道器不需要配置版本

  2. 閘道器->服務A(V1.0),閘道器配給服務A(V1.0)的100%權重(流量)

灰度釋出中:
  1. 上線服務A(V1.1)

  2. 在閘道器層調撥10%權重(流量)給A(V1.1),給A(V1.0)的權重(流量)減少到90%

  3. 透過觀測確認灰度有效,把A(V1.0)的權重(流量)全部切換到A(V1.1)

灰度釋出後:
  1. 下線服務A(V1.0),灰度成功


基於閘道器版本切換的灰度釋出
灰度釋出前:
  1. 假設當前生產環境,呼叫路徑為閘道器(V1.0)->服務A(V1.0)

  2. 運維將釋出新的生產環境,部署新服務叢集,服務A(V1.1)

  3. 由於閘道器(1.0)並未指向服務A(V1.1),所以它們是不能被呼叫的

灰度釋出中:
  1. 新增用作灰度釋出的閘道器(V1.1),指向服務A(V1.1)

  2. 灰度閘道器(V1.1)釋出到服務註冊發現中心,但禁止被服務發現,閘道器外的呼叫進來無法負載均衡到閘道器(V1.1)上

  3. 在灰度閘道器(V1.1)->服務A(V1.1)這條呼叫路徑做灰度測試

  4. 灰度測試成功後,把閘道器(V1.0)指向服務A(V1.1)

灰度釋出後:
  1. 下線服務A(V1.0),灰度成功

  2. 灰度閘道器(V1.1)可以不用下線,留作下次版本上線再次灰度釋出

  3. 如果您對新服務比較自信,可以更簡化,可以不用灰度閘道器和灰度測試,當服務A(V1.1)上線後,原有閘道器直接指向服務A(V1.1),然後下線服務A(V1.0)

戶自定義和程式設計灰度路由策略

使用者可以實現跟業務有關的路由策略,根據業務引數的不同,負載均衡到不同的伺服器。
  1. 基於服務的程式設計灰度路由,實現DiscoveryEnabledExtension,透過RequestContextHolder(獲取來自閘道器的Header引數)和ServiceStrategyContext(獲取來自RPC方式的方法引數)獲取業務背景關係引數,進行路由自定義

  2. 基於Zuul的程式設計灰度路由,實現DiscoveryEnabledExtension,透過Zuul自帶的RequestContext(獲取來自閘道器的Header引數)獲取業務背景關係引數,進行路由自定義

  3. 基於Spring Cloud Api Gateway的程式設計灰度路由,實現DiscoveryEnabledExtension,透過GatewayStrategyContext(獲取來自閘道器的Header引數)獲取業務背景關係引數,進行路由自定義

舉例如下:
基於Rest請求Header頭部自帶版本規則的實時動態路由:
場景舉例:外部->閘道器->A服務,A服務有a1, a2兩個實體,a1和a2是一模一樣的兩個服務,但a1和a2的區別是連的資料庫是不同的。a1對接外部的民生銀行通道,a2對接外部的浦發銀行通道。那麼外部Rest經過閘道器時,自動根據版本號路由到正確的服務上。
如圖所示,當前從Postman發起的請求,當走到example-a服務的時候,只能走1.0版本,到example-b服務的時候,也是1.0版本,到example-c服務的時候,能路由到1.1和1.2版本,下麵的打印出來的結果印證了這個路由方式。
加如下程式碼即可實現該功能:
@Bean
public MyDiscoveryEnabledExtension myDiscoveryEnabledExtension() {
    return new MyDiscoveryEnabledExtension();
}

表示在閘道器層(以Zuul為例),程式設計灰度路由策略,如下程式碼,表示請求的Header中的token包含’abc’,在負載均衡層面,對應的服務示例不會被負載均衡到。
程式碼截圖:
// 實現了組合策略,版本路由策略+自定義策略
public class MyDiscoveryEnabledExtension implements DiscoveryEnabledExtension {
    private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledExtension.class);

    @Override
    public boolean apply(Server server, Map<StringString> metadata) {
        // 對Rest呼叫傳來的Header引數(例如Token)做策略
        return applyFromHeader(server, metadata);
    }

    // 根據Rest呼叫傳來的Header引數(例如Token),選取執行呼叫請求的服務實體
    private boolean applyFromHeader(Server server, Map<StringString> metadata) {
        RequestContext context = RequestContext.getCurrentContext();
        String token = context.getRequest().getHeader("token");
        // String value = context.getRequest().getParameter("value");

        String serviceId = server.getMetaInfo().getAppName().toLowerCase();

        LOG.info("Zuul端負載均衡使用者定製觸發:serviceId={}, host={}, metadata={}, context={}", serviceId, server.toString(), metadata, context);

        String filterToken = "abc";
        if (StringUtils.isNotEmpty(token) && token.contains(filterToken)) {
            LOG.info("過濾條件:當Token含有'{}'的時候,不能被Ribbon負載均衡到", filterToken);

            return false;
        }

        return true;
    }
}

表示在服務層,當服務名為discovery-springcloud-example-c,同時版本為1.0,同時引數value中包含’abc’,三個條件同時滿足的情況下,在負載均衡層面,對應的服務示例不會被負載均衡到。
程式碼截圖:
// 實現了組合策略,版本路由策略+自定義策略
public class MyDiscoveryEnabledExtension implements DiscoveryEnabledExtension {
    private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledExtension.class);

    @Override
    public boolean apply(Server server, Map metadata) {
        // 對RPC呼叫傳來的方法引數做策略
        return applyFromMethd(server, metadata);
    }

// 根據RPC呼叫傳來的方法引數(例如介面名、方法名、引數名或引數值等),選取執行呼叫請求的服務。

    @SuppressWarnings("unchecked")
    private boolean applyFromMethd(Server server, Map<StringString> metadata) {
        ServiceStrategyContext context = ServiceStrategyContext.getCurrentContext();
        Map<StringObject> attributes = context.getAttributes();

        String serviceId = server.getMetaInfo().getAppName().toLowerCase();
        String version = metadata.get(DiscoveryConstant.VERSION);

        LOG.info("Serivice端負載均衡使用者定製觸發:serviceId={}, host={}, metadata={}, context={}", serviceId, server.toString(), metadata, context);

        String filterServiceId = "discovery-springcloud-example-b";
        String filterVersion = "1.0";
        String filterBusinessValue = "abc";
        if (StringUtils.equals(serviceId, filterServiceId) && StringUtils.equals(version, filterVersion)) {
            if (attributes.containsKey(ServiceStrategyConstant.PARAMETER_MAP)) {
                Map<StringObject> parameterMap = (Map<StringObject>) attributes.get(ServiceStrategyConstant.PARAMETER_MAP);
                String value = parameterMap.get("value").toString();
                if (StringUtils.isNotEmpty(value) && value.contains(filterBusinessValue)) {
                    LOG.info("過濾條件:當serviceId={} && version={} && 業務引數含有'{}'的時候,不能被Ribbon負載均衡到", filterServiceId, filterVersion, filterBusinessValue);

                    return false;
                }
            }
        }

        return true;
    }
}
使用者可以透過訂閱業務引數的變化,實現特色化的灰度釋出,例如,多資料源的資料庫切換的灰度釋出。
程式碼截圖:
@EventBus
public class MySubscriber {
    @Autowired
    private PluginAdapter pluginAdapter;

    @Subscribe
    public void onCustomization(CustomizationEvent customizationEvent) {
        CustomizationEntity customizationEntity = customizationEvent.getCustomizationEntity();
        String serviceId = pluginAdapter.getServiceId();
        if (customizationEntity != null) {
            Map<StringMap<StringString>> customizationMap = customizationEntity.getCustomizationMap();
            Map<StringString> customizationParameter = customizationMap.get(serviceId);
            // 根據customizationParameter的引數動態切換資料源
        } else {
            // 根據customizationParameter的引數動態切換資料源
        }
    }

    @Subscribe
    public void onRegisterFailure(RegisterFailureEvent registerFailureEvent) {
        System.out.println("========== 註冊失敗, eventType=" + registerFailureEvent.getEventType() + ", eventDescription=" + registerFailureEvent.getEventDescription() + ", serviceId=" + registerFailureEvent.getServiceId() + ", host=" + registerFailureEvent.getHost() + ", port=" + registerFailureEvent.getPort());
    }
}

使用者自定義灰度釋出監聽

使用者實現服務註冊的監聽,當負服務註冊的時候,使用者會收到下麵的事件。
程式碼截圖:
public class MyRegisterListener extends AbstractRegisterListener {
    @Override
    public void onRegister(Registration registration) {
    }

    @Override
    public void onDeregister(Registration registration) {
    }

    @Override
    public void onSetStatus(Registration registration, String status) {
    }

    @Override
    public void onClose() {
    }
}

使用者實現服務發現的監聽,當負服務發現過濾的時候,使用者會收到下麵的事件。
程式碼截圖:

public class MyDiscoveryListener extends AbstractDiscoveryListener {
    @Override
    public void onGetInstances(String serviceId, List instances) {
    }

    @Override
    public void onGetServices(List services) {
    }
}

使用者實現負載均衡的監聽,當負載均衡啟動過濾的時候,使用者會收到下麵的事件。
程式碼截圖:
public class MyLoadBalanceListener extends AbstractLoadBalanceListener {
    @Override
    public void onGetServers(String serviceId, List extends Server> servers) {
    }
}

使用者實現對註冊失敗的監聽,當黑名單啟用的時候,會觸發註冊失敗,那麼使用者會收到一個註冊失敗的事件。
程式碼截圖:

@EventBus
public class MySubscriber {
    @Subscribe
    public void onRegisterFailure(RegisterFailureEvent registerFailureEvent) {
    }
}

圖形化灰度釋出桌面程式:
具體操作不詳細述說了,請看相關影片:
灰度釋出-版本訪問策略:
請訪問http://www.iqiyi.com/w_19rzwzovrl.html,影片清晰度改成720P,然後最大化播放。
灰度釋出-版本權重策略:
請訪問http://www.iqiyi.com/w_19rzs9pll1.html,影片清晰度改成720P,然後最大化播放。
灰度釋出-全鏈路策略:
請訪問http://www.iqiyi.com/w_19s1e0zf95.html,影片清晰度改成720P,然後最大化播放。
跟Spring Boot Admin整合

實現相關一系列的監控:

3天Kubernetes線下實戰培訓

Kubernetes應用實戰培訓將於2018年9月14日在上海開課,3天時間帶你係統掌握Kubernetes本次培訓包括:容器特性、映象、網路;Docker特性、架構、元件、概念、Runtime;Docker安全;Docker實踐;Kubernetes架構、核心元件、基本功能;Kubernetes設計理念、架構設計、基本功能、常用物件、設計原則;Kubernetes的實踐、執行時、網路、外掛已經落地經驗;微服務架構、DevOps等,點選下方圖片檢視詳情。

贊(0)

分享創造快樂