1. 程式人生 > >Nepxion Discovery【探索】微服務企業級解決方案

Nepxion Discovery【探索】微服務企業級解決方案

Alt text

Nepxion Discovery【探索】微服務企業級解決方案】

Nepxion Discovery【探索】使用指南,基於Spring Cloud Greenwich版、Finchley版和Hoxton版而製作,對於Edgware版,使用者需要自行修改。使用指南主要涉及的功能包括:

  • 基於Header傳遞的全鏈路灰度路由,閘道器為路由觸發點。採用配置中心配置路由規則對映在閘道器過濾器中植入Header資訊而實現,路由規則傳遞到全鏈路服務中。路由方式主要包括版本和區域的匹配路由、版本和區域的權重路由、基於機器IP地址和埠的路由
  • 基於規則訂閱的全鏈路灰度釋出。採用配置中心配置灰度規則對映在全鏈路服務而實現,所有服務都訂閱某個共享配置。釋出方式主要包括版本和區域的匹配發布、版本和區域的權重發布
  • 全鏈路服務隔離。包括註冊隔離、消費端隔離和提供端服務隔離,示例僅提供基於Group隔離。除此之外,不在本文介紹內的,還包括:
    • 註冊隔離:黑/白名單的IP地址的註冊隔離、最大註冊數限制的註冊隔離
    • 消費端隔離:黑/白名單的IP地址的消費端隔離
  • 全鏈路服務限流熔斷降級許可權,整合阿里巴巴Sentinel,有機整合灰度路由,擴充套件LimitApp的機制,通過動態的Http Header方式實現組合式防護機制,包括基於服務名、基於灰度組、基於灰度版本、基於灰度區域、基於機器地址和埠等防護機制,支援自定義任意的業務引數組合實現該功能。支援原生的流控規則、降級規則、授權規則、系統規則、熱點引數流控規則
  • 全鏈路灰度呼叫鏈。包括Header方式和日誌方式,Header方式框架內部整合,日誌方式通過MDC輸出(需使用者自行整合)
  • 同城雙活多機房切換支援。它包含在“基於Header傳遞的全鏈路灰度路由”裡
  • 資料庫灰度釋出。內建簡單的資料庫灰度釋出策略,它不在本文的介紹範圍內
  • 灰度路由和釋出的自動化測試
  • Docker容器化和Kubernetes平臺的無縫支援部署

[Nacos] 阿里巴巴中介軟體部門開發的新一代集服務註冊發現中心和配置中心為一體的中介軟體。它是構建以“服務”為中心的現代應用架構 (例如微服務正規化、雲原生正規化) 的服務基礎設施,支援幾乎所有主流型別的“服務”的發現、配置和管理,更敏捷和容易地構建、交付和管理微服務平臺

[Sentinel] 阿里巴巴中介軟體部門開發的新一代以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性的分散式系統的流量防衛兵。它承接了阿里巴巴近10年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、訊息削峰填谷、叢集流量控制、實時熔斷下游不可用應用等

[Spring Cloud Alibaba] 阿里巴巴中介軟體部門開發的Spring Cloud增強套件,致力於提供微服務開發的一站式解決方案。此專案包含開發分散式應用微服務的必需元件,方便開發者通過Spring Cloud程式設計模型輕鬆使用這些元件來開發分散式應用服務。依託Spring Cloud Alibaba,只需要新增一些註解和少量配置,就可以將Spring Cloud應用接入阿里微服務解決方案,通過阿里中介軟體來迅速搭建分散式應用系統

示例以Nacos為服務註冊中心和配置中心(使用者可自行換成其它服務註冊中心和配置中心),整合Spring Cloud Alibaba,通過Gateway和Zuul呼叫兩個版本或者區域的服務,模擬閘道器灰度路由和服務灰度權重的功能

如果使用者需要更強大的功能,請參考原始碼主頁。規則策略很多,請使用者選擇最適合自己業務場景的方式

目錄

請聯絡我

微信、公眾號和文件

Alt textAlt textAlt text

相關連結

原始碼主頁

原始碼主頁

指南主頁

指南主頁

文件主頁

文件主頁

相關圖示

部署架構拓撲圖

Alt text

服務治理架構圖

Alt text

灰度方式區別圖

Alt text

環境搭建

啟動服務

  • 在IDE中,啟動四個應用服務和兩個閘道器服務,如下:
類名 微服務 服務埠 版本 區域
DiscoveryGuideServiceA1.java A1 3001 1.0 dev
DiscoveryGuideServiceA2.java A2 3002 1.1 qa
DiscoveryGuideServiceB1.java B1 4001 1.0 qa
DiscoveryGuideServiceB2.java B2 4002 1.1 dev
DiscoveryGuideGateway.java Gateway 5001 1.0
DiscoveryGuideZuul.java Zuul 5002 1.0

注:啟動不分前後次序

環境驗證

  • 匯入Postman的測試指令碼,指令碼地址

  • 在Postman中執行目錄結構下 ”Nepxion“ -> ”Discovery指南閘道器介面“ -> ”Gateway閘道器呼叫示例“,呼叫地址為http://localhost:5001/discovery-guide-service-a/invoke/gateway,相關的Header值已經預設,供開發者修改。測試通過Spring Cloud Gateway閘道器的呼叫結果,結果為如下格式:

gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] 
-> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
  • 在Postman中執行目錄結構下 ”Nepxion“ -> ”Discovery指南閘道器介面“ -> ”Zuul閘道器呼叫示例“,呼叫地址為http://localhost:5002/discovery-guide-service-a/invoke/zuul,相關的Header值已經預設,供開發者修改。測試通過Zuul閘道器的呼叫結果,結果為如下格式:
zuul -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] 
-> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
  • 上述步驟在下面每次更改規則策略的時候執行,並驗證結果和規則策略的期望值是否相同

基於Header傳遞方式的閘道器灰度路由策略

灰度路由架構圖

多版本灰度路由架構圖

Alt text

多區域灰度路由架構圖

Alt text

多IP和埠灰度路由架構圖

Alt text

配置閘道器灰度路由策略

在Nacos配置中心,增加閘道器灰度路由策略

版本匹配灰度路由策略

增加Spring Cloud Gateway的基於版本匹配路由的灰度策略,Group為discovery-guide-group,Data Id為discovery-guide-gateway,策略內容如下,實現從Spring Cloud Gateway發起的呼叫都走版本為1.0的服務:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <strategy>
        <version>1.0</version>
    </strategy>
</rule>

Alt text

每個服務呼叫的版本都可以自行指定,見下面第二條。當所有服務都選同一版本的時候,可以簡化成下面第一條

1. <version>1.0</version>
2. <version>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version>

如果上述表示式還未滿足需求,也可以採用萬用字元(具體詳細用法,參考Spring AntPathMatcher)

* - 表示呼叫範圍為所有服務的所有版本
1.* - 表示呼叫範圍為所有服務的1開頭的所有版本

或者

"discovery-guide-service-b":"1.*;1.2.?"

表示discovery-guide-service-b服務的版本呼叫範圍是1開頭的所有版本,或者是1.2開頭的所有版本(末尾必須是1個字元)

版本權重灰度路由策略

增加Spring Cloud Gateway的基於版本權重路由的灰度策略,Group為discovery-guide-group,Data Id為discovery-guide-gateway,策略內容如下,實現從Spring Cloud Gateway發起的呼叫1.0版本流量呼叫為90%,1.1流量呼叫為10%:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <strategy>
        <version-weight>1.0=90;1.1=10</version-weight>
    </strategy>
</rule>

Alt text

每個服務呼叫的版本權重都可以自行指定,見下面第二條。當所有服務都選相同版本權重的時候,可以簡化成下面第一條

1. <version-weight>1.0=90;1.1=10</version-weight>
2. <version-weight>{"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"}</version-weight>

區域匹配灰度路由策略

增加Zuul的基於區域匹配路由的灰度策略,Group為discovery-guide-group,Data Id為discovery-guide-zuul,策略內容如下,實現從Zuul發起的呼叫都走區域為dev的服務:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <strategy>
        <region>dev</region>
    </strategy>
</rule>

Alt text

每個服務呼叫的區域都可以自行指定,見下面第二條。當所有服務都選同一區域的時候,可以簡化成下面第一條

1. <region>dev</region>
2. <region>{"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"}</region>

如果上述表示式還未滿足需求,也可以採用萬用字元(具體詳細用法,參考Spring AntPathMatcher)

* - 表示呼叫範圍為所有服務的所有區域
d* - 表示呼叫範圍為所有服務的d開頭的所有區域

或者

"discovery-guide-service-b":"d*;q?"

表示discovery-guide-service-b服務的區域呼叫範圍是d開頭的所有區域,或者是q開頭的所有區域(末尾必須是1個字元)

區域權重灰度路由策略

增加Zuul的基於區域權重路由的灰度策略,Group為discovery-guide-group,Data Id為discovery-guide-zuul,策略內容如下,實現從Zuul發起的呼叫dev區域流量呼叫為85%,qa區域流量呼叫為15%:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <strategy>
        <region-weight>dev=85;qa=15</region-weight>
    </strategy>
</rule>

Alt text

每個服務呼叫的區域權重都可以自行指定,見下面第二條。當所有服務都選相同區域權重的時候,可以簡化成下面第一條

1. <region-weight>dev=85;qa=15</region-weight>
2. <region-weight>{"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=85;qa=15"}</region-weight>

通過其它方式設定閘道器灰度路由策略

除了上面通過配置中心釋出灰度規路由則外,還有如下三種方式:

通過前端傳入灰度路由策略

通過前端(Postman)方式傳入灰度路由策略,來代替配置中心方式,傳遞全鏈路路由策略。注意:當配置中心和介面都配置後,以介面傳入優先

  • 版本匹配策略,Header格式如下任選一個:
1. n-d-version=1.0
2. n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
  • 版本權重策略,Header格式如下任選一個:
1. n-d-version-weight=1.0=90;1.1=10
2. n-d-version-weight={"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"}
  • 區域匹配策略,Header格式如下任選一個:
1. n-d-region=qa
2. n-d-region={"discovery-guide-service-a":"qa", "discovery-guide-service-b":"qa"}
  • 區域權重策略,Header格式如下任選一個:
1. n-d-region-weight=dev=99;qa=1
2. n-d-region-weight={"discovery-guide-service-a":"dev=99;qa=1", "discovery-guide-service-b":"dev=99;qa=1"}
  • 機器IP地址和埠策略:
n-d-address={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"}

Alt text

Alt text

當外界傳值Header的時候,閘道器也設定並傳遞同名的Header,需要決定哪個Header傳遞到後邊的服務去。需要通過如下開關做控制:

# 當外界傳值Header的時候,閘道器也設定並傳遞同名的Header,需要決定哪個Header傳遞到後邊的服務去。如果下面開關為true,以閘道器設定為優先,否則以外界傳值為優先。缺失則預設為true
spring.application.strategy.gateway.header.priority=false
# 當以閘道器設定為優先的時候,閘道器未配置Header,而外界配置了Header,仍舊忽略外界的Header。缺失則預設為true
spring.application.strategy.gateway.original.header.ignored=true

# 當外界傳值Header的時候,閘道器也設定並傳遞同名的Header,需要決定哪個Header傳遞到後邊的服務去。如果下面開關為true,以閘道器設定為優先,否則以外界傳值為優先。缺失則預設為true
spring.application.strategy.zuul.header.priority=false
# 當以閘道器設定為優先的時候,閘道器未配置Header,而外界配置了Header,仍舊忽略外界的Header。缺失則預設為true
spring.application.strategy.zuul.original.header.ignored=true

通過業務引數在閘道器過濾器中自定義灰度路由策略

通過閘道器過濾器傳遞Http Header的方式傳遞全鏈路灰度路由策略。下面程式碼只適用於Zuul和Spring Cloud Gateway閘道器,Service微服務不需要加該方式

  • 內建策略解析對映到過濾器的自定義方式

增加Spring Cloud Gateway的解析策略,Group為discovery-guide-group,Data Id為discovery-guide-gateway,或者,增加Spring Cloud Gateway的解析策略,Group為discovery-guide-group,Data Id為discovery-guide-zuul,策略內容見下面XML內容,它所表達的功能邏輯:

1. 當外部呼叫帶有的Http Header中的值a=1同時b=2
   <condition>節點中header="a=1;b=2"對應的version-id="version-route1",找到下面
   <route>節點中id="version-route1" type="version"的那項,那麼路由即為:
   {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}

2. 當外部呼叫帶有的Http Header中的值a=1
   <condition>節點中header="a=1"對應的version-id="version-route2",找到下面
   <route>中id="version-route2" type="version"的那項,那麼路由即為:
   {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"}

3. 當外部呼叫帶有的Http Header中的值都不命中,找到上面
   <strategy>節點中的全域性預設路由,那麼路由即為:
   {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}

4. 策略解析總共支援5種,可以單獨一項使用,也可以多項疊加使用:
   1)version 版本路由
   2)region 區域路由
   3)address 機器地址路由
   4)version-weight 版本權重路由
   5)region-weight 區域權重路由
<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <!-- 基於Http Header傳遞的策略路由,全域性預設路由 -->
    <strategy>
        <version>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version>
    </strategy>

    <!-- 基於Http Header傳遞的策略路由,客戶定製化控制,跟業務引數繫結。如果不命中,則執行上面的全域性預設路由 -->
    <strategy-customization>
        <conditions>
            <condition id="condition1" header="a=1" version-id="version-route2"/>
            <condition id="condition2" header="a=1;b=2" version-id="version-route1"/>
        </conditions>

        <routes>
            <route id="version-route1" type="version">{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</route>
            <route id="version-route2" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"}</route>
        </routes>
    </strategy-customization>
</rule>

Alt text

  • 使用者覆蓋過濾器的自定義方式

繼承GatewayStrategyRouteFilter或者ZuulStrategyRouteFilter,覆蓋掉如下方法中的一個或者多個,通過@Bean方式覆蓋框架內建的過濾類

protected String getRouteVersion();

protected String getRouteRegion();

protected String getRouteAddress();

GatewayStrategyRouteFilter示例

在程式碼不同的Header選擇不同的路由路徑

// 適用於A/B Testing或者更根據某業務引數決定灰度路由路徑。可以結合配置中心分別配置A/B兩條路徑,可以動態改變並通知
// 當Header中傳來的使用者為張三,執行一條路由路徑;為李四,執行另一條路由路徑
public class MyGatewayStrategyRouteFilter extends DefaultGatewayStrategyRouteFilter {
    private static final String DEFAULT_A_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.0\", \"discovery-guide-service-b\":\"1.1\"}";
    private static final String DEFAULT_B_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.1\", \"discovery-guide-service-b\":\"1.0\"}";

    @Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}")
    private String aRouteVersion;

    @Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}")
    private String bRouteVersion;

    @Override
    public String getRouteVersion() {
        String user = strategyContextHolder.getHeader("user");

        if (StringUtils.equals(user, "zhangsan")) {
            return aRouteVersion;
        } else if (StringUtils.equals(user, "lisi")) {
            return bRouteVersion;
        }

        return super.getRouteVersion();
    }
}

在配置類裡@Bean方式進行過濾類建立,覆蓋框架內建的過濾類

@Bean
public GatewayStrategyRouteFilter gatewayStrategyRouteFilter() {
    return new MyGatewayStrategyRouteFilter();
}

ZuulStrategyRouteFilter示例

在程式碼中 ,根據不同的Header選擇不同的路由路徑

// 適用於A/B Testing或者更根據某業務引數決定灰度路由路徑。可以結合配置中心分別配置A/B兩條路徑,可以動態改變並通知
// 當Header中傳來的使用者為張三,執行一條路由路徑;為李四,執行另一條路由路徑
public class MyZuulStrategyRouteFilter extends DefaultZuulStrategyRouteFilter {
    private static final String DEFAULT_A_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.0\", \"discovery-guide-service-b\":\"1.1\"}";
    private static final String DEFAULT_B_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.1\", \"discovery-guide-service-b\":\"1.0\"}";

    @Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}")
    private String aRouteVersion;

    @Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}")
    private String bRouteVersion;

    @Override
    public String getRouteVersion() {
        String user = strategyContextHolder.getHeader("user");

        if (StringUtils.equals(user, "zhangsan")) {
            return aRouteVersion;
        } else if (StringUtils.equals(user, "lisi")) {
            return bRouteVersion;
        }

        return super.getRouteVersion();
    }
}

在配置類裡@Bean方式進行過濾類建立,覆蓋框架內建的過濾類

@Bean
public ZuulStrategyRouteFilter zuulStrategyRouteFilter() {
    return new MyZuulStrategyRouteFilter();
}

通過業務引數在策略類中自定義灰度路由策略

通過策略方式自定義灰度路由策略。下面程式碼既適用於Zuul和Spring Cloud Gateway閘道器,也適用於Service微服務,同時全鏈路中閘道器和服務都必須加該方式

// 實現了組合策略,版本路由策略+區域路由策略+IP和埠路由策略+自定義策略
public class MyDiscoveryEnabledStrategy extends DefaultDiscoveryEnabledStrategy {
    private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledStrategy.class);

    // 對Rest呼叫傳來的Header引數(例如:mobile)做策略
    @Override
    public boolean apply(Server server) {
        String mobile = strategyContextHolder.getHeader("mobile");
        String serviceId = pluginAdapter.getServerServiceId(server);
        String version = pluginAdapter.getServerVersion(server);
        String region = pluginAdapter.getServerRegion(server);

        LOG.info("負載均衡使用者定製觸發:mobile={}, serviceId={}, version={}, region={}", mobile, serviceId, version, region);

        if (StringUtils.isNotEmpty(mobile)) {
            // 手機號以移動138開頭,路由到1.0版本的服務上
            if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) {
                return true;
                // 手機號以聯通133開頭,路由到2.0版本的服務上
            } else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) {
                return true;
            } else {
                // 其它情況,直接拒絕請求
                return false;
            }
        }

        return true;
    }
}

在配置類裡@Bean方式進行策略類建立

@Bean
public DiscoveryEnabledStrategy discoveryEnabledStrategy() {
    return new MyDiscoveryEnabledStrategy();
}

在閘道器和服務中支援基於Rest Header傳遞的自定義灰度路由策略,除此之外,服務還支援基於Rpc方法引數傳遞的自定義灰度路由策略,它包括介面名、方法名、引數名或引數值等多種形式。下面的示例表示在服務中同時開啟基於Rest Header傳遞和Rpc方法引數傳遞的自定義組合式灰度路由策略

// 實現了組合策略,版本路由策略+區域路由策略+IP和埠路由策略+自定義策略
public class MyDiscoveryEnabledStrategy implements DiscoveryEnabledStrategy {
    private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledStrategy.class);

    @Autowired
    private PluginAdapter pluginAdapter;

    @Autowired
    private ServiceStrategyContextHolder serviceStrategyContextHolder;

    @Override
    public boolean apply(Server server) {
        boolean enabled = applyFromHeader(server);
        if (!enabled) {
            return false;
        }

        return applyFromMethod(server);
    }

    // 根據Rest呼叫傳來的Header引數(例如:mobile),選取執行呼叫請求的服務例項
    private boolean applyFromHeader(Server server) {
        String mobile = serviceStrategyContextHolder.getHeader("mobile");
        String serviceId = pluginAdapter.getServerServiceId(server);
        String version = pluginAdapter.getServerVersion(server);
        String region = pluginAdapter.getServerRegion(server);

        LOG.info("負載均衡使用者定製觸發:mobile={}, serviceId={}, version={}, region={}", mobile, serviceId, version, region);

        if (StringUtils.isNotEmpty(mobile)) {
            // 手機號以移動138開頭,路由到1.0版本的服務上
            if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) {
                return true;
                // 手機號以聯通133開頭,路由到2.0版本的服務上
            } else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) {
                return true;
            } else {
                // 其它情況,直接拒絕請求
                return false;
            }
        }

        return true;
    }

    // 根據RPC呼叫傳來的方法引數(例如介面名、方法名、引數名或引數值等),選取執行呼叫請求的服務例項
    // 本示例只作用在discovery-guide-service-a服務上
    @SuppressWarnings("unchecked")
    private boolean applyFromMethod(Server server) {
        Map<String, Object> attributes = serviceStrategyContextHolder.getRpcAttributes();
        String serviceId = pluginAdapter.getServerServiceId(server);
        String version = pluginAdapter.getServerVersion(server);
        String region = pluginAdapter.getServerRegion(server);

        LOG.info("負載均衡使用者定製觸發:attributes={}, serviceId={}, version={}, region={}", attributes, serviceId, version, region);

        if (attributes.containsKey(ServiceStrategyConstant.PARAMETER_MAP)) {
            Map<String, Object> parameterMap = (Map<String, Object>) attributes.get(ServiceStrategyConstant.PARAMETER_MAP);
            String value = parameterMap.get("value").toString();
            if (StringUtils.isNotEmpty(value)) {
                // 輸入值包含dev,路由到dev區域的服務上
                if (value.contains("dev") && StringUtils.equals(region, "dev")) {
                    return true;
                    // 輸入值包含qa,路由到qa區域的服務上
                } else if (value.contains("qa") && StringUtils.equals(region, "qa")) {
                    return true;
                } else {
                    // 其它情況,直接通過請求
                    return true;
                }
            }
        }

        return true;
    }
}

需要通過如下開關開啟該功能

# 啟動和關閉路由策略的時候,對RPC方式的呼叫攔截。缺失則預設為false
spring.application.strategy.rpc.intercept.enabled=true

配置前端灰度&閘道器灰度路由組合式策略

當前端(例如:APP)和後端微服務同時存在多個版本時,可以執行“前端灰度&閘道器灰度路由組合式策略”

例如:前端存在1.0和2.0版本,微服務存在1.0和2.0版本,由於存在版本不相容的情況(前端1.0版本只能呼叫微服務的1.0版本,前端2.0版本只能呼叫微服務的2.0版本),那麼前端呼叫閘道器時候,可以通過Header傳遞它的版本號給閘道器,閘道器根據前端版本號,去路由對應版本的微服務

該場景可以用“通過業務引數在閘道器過濾器中自定義灰度路由策略”的方案來解決,如下:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <strategy-customization>
        <conditions>
            <condition id="condition1" header="app-version=1.0" version-id="version-route1"/>
            <condition id="condition2" header="app-version=2.0" version-id="version-route2"/>
        </conditions>

        <routes>
            <route id="version-route1" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</route>
            <route id="version-route2" type="version">{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</route>
        </routes>
    </strategy-customization>
</rule>

上述配置,模擬出全鏈路中,兩條獨立不受干擾的呼叫路徑:

1. APP v1.0 -> 閘道器 -> A服務 v1.0 -> B服務 v1.0
2. APP v1.1 -> 閘道器 -> A服務 v1.1 -> B服務 v1.1

基於訂閱方式的全鏈路灰度釋出規則

在Nacos配置中心,增加全鏈路灰度釋出規則 注意:該功能和閘道器灰度路由和灰度權重功能會疊加,為了不影響演示效果,請先清除閘道器灰度路由的策略

配置全鏈路灰度匹配規則

版本匹配灰度規則

增加版本匹配的灰度規則,Group為discovery-guide-group,Data Id為discovery-guide-group(全域性釋出,兩者都是組名),規則內容如下,實現a服務1.0版本只能訪問b服務1.0版本,a服務1.1版本只能訪問b服務1.1版本:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <discovery>
        <version>
            <service consumer-service-name="discovery-guide-service-a" provider-service-name="discovery-guide-service-b" consumer-version-value="1.0" provider-version-value="1.0"/>
            <service consumer-service-name="discovery-guide-service-a" provider-service-name="discovery-guide-service-b" consumer-version-value="1.1" provider-version-value="1.1"/>
        </version>
    </discovery>
</rule>

Alt text

區域匹配灰度規則

增加區域匹配的灰度規則,Group為discovery-guide-group,Data Id為discovery-guide-group(全域性釋出,兩者都是組名),規則內容如下,實現dev區域的a服務只能訪問dev區域的b服務,qa區域的a服務只能訪問qa區域的b服務:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <discovery>
        <region>
            <service consumer-service-name="discovery-guide-service-a" provider-service-name="discovery-guide-service-b" consumer-region-value="dev" provider-region-value="dev"/>
            <service consumer-service-name="discovery-guide-service-a" provider-service-name="discovery-guide-service-b" consumer-region-value="qa" provider-region-value="qa"/>
        </region>
    </discovery>
</rule>

Alt text

配置全鏈路灰度權重規則

全域性版本權重灰度規則

增加全域性版本權重的灰度規則,Group為discovery-guide-group,Data Id為discovery-guide-group(全域性釋出,兩者都是組名),規則內容如下,實現版本為1.0的服務提供90%的流量,版本為1.1的服務提供10%的流量:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <discovery>
        <weight>
            <version provider-weight-value="1.0=90;1.1=10"/>
        </weight>
    </discovery>
</rule>

Alt text

區域性版本權重灰度規則

增加區域性版本權重的灰度規則,Group為discovery-guide-group,Data Id為discovery-guide-group(全域性釋出,兩者都是組名),規則內容如下,實現a服務1.0版本提供90%的流量,1.1版本提供10%的流量;b服務1.0版本提供20%的流量,1.1版本提供80%的流量:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <discovery>
        <weight>
            <service provider-service-name="discovery-guide-service-a" provider-weight-value="1.0=90;1.1=10" type="version"/>
            <service provider-service-name="discovery-guide-service-b" provider-weight-value="1.0=20;1.1=80" type="version"/>
        </weight>
    </discovery>
</rule>

Alt text

全域性區域權重灰度規則

增加全域性區域權重的灰度規則,Group為discovery-guide-group,Data Id為discovery-guide-group(全域性釋出,兩者都是組名),規則內容如下,實現區域為dev的服務提供90%的流量,區域為qa的服務提供10%的流量:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <discovery>
        <weight>
            <region provider-weight-value="dev=90;qa=10"/>
        </weight>
    </discovery>
</rule>

Alt text

區域性區域權重灰度規則

增加區域性區域權重的灰度規則,Group為discovery-guide-group,Data Id為discovery-guide-group(全域性釋出,兩者都是組名),規則內容如下,實現a服務dev區域提供90%的流量,qa區域提供10%的流量;b服務dev區域提供20%的流量,qa區域提供80%的流量:

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <discovery>
        <weight>
            <service provider-service-name="discovery-guide-service-a" provider-weight-value="dev=90;qa=10" type="region"/>
            <service provider-service-name="discovery-guide-service-b" provider-weight-value="dev=20;qa=80" type="region"/>
        </weight>
    </discovery>
</rule>

Alt text

注意:區域性權重優先順序高於全域性權重,版本權重優先順序高於區域權重

請執行Postman操作,請仔細觀察服務被隨機權重呼叫到的概率

配置全鏈路灰度權重&灰度版本組合式規則

增加組合式的灰度規則,Group為discovery-guide-group,Data Id為discovery-guide-group(全域性釋出,兩者都是組名),規則內容如下,實現功能:

  • a服務1.0版本向閘道器提供90%的流量,1.1版本向閘道器提供10%的流量
  • a服務1.0版本只能訪問b服務1.0版本,1.1版本只能訪問b服務1.1版本

該功能的意義是,閘道器隨機權重呼叫服務,而服務鏈路按照版本匹配方式呼叫

<?xml version="1.0" encoding="UTF-8"?>
<rule>
    <discovery>
        <version>
            <service consumer-service-name="discovery-guide-service-a" provider-service-name="discovery-guide-service-b" consumer-version-value="1.0" provider-version-value="1.0"/>
            <service consumer-service-name="discovery-guide-service-a" provider-service-name="discovery-guide-service-b" consumer-version-value="1.1" provider-version-value="1.1"/>
        </version>

        <weight>
            <service consumer-service-name="discovery-guide-gateway" provider-service-name="discovery-guide-service-a" provider-weight-value="1.0=90;1.1=10" type="version"/>
            <service consumer-service-name="discovery-guide-zuul" provider-service-name="discovery-guide-service-a" provider-weight-value="1.0=90;1.1=10" type="version"/>
        </weight>
    </discovery>
</rule>

Alt text

圖形化介面驗證

  • 下載原始碼主頁的工程,並匯入IDE
  • 啟動原始碼工程下的discovery-springcloud-example-console/ConsoleApplication
  • 啟動原始碼工程下的discovery-console-desktop/ConsoleLauncher
  • 通過admin/admin登入,點選“顯示服務拓撲”按鈕,將呈現如下介面 Alt text
  • 在加入上述規則前,選中閘道器節點,右鍵點選“執行灰度路由”,在彈出路由介面中,依次加入“discovery-guide-service-a”和“discovery-guide-service-b”,點選“執行路由”按鈕,將呈現如下介面 Alt text
  • 在加入上述規則後,在路由介面中,再次點選“執行路由”按鈕,將呈現如下介面 Alt text

全鏈路服務隔離

元資料中的Group在一定意義上代表著系統ID或者系統邏輯分組,基於Group策略意味著只有同一個系統中的服務才能呼叫

註冊服務隔離

基於Group黑/白名單的策略,即當前的服務所在的Group,不在Group的黑名單或者在白名單裡,才允許被註冊。只需要在閘道器或者服務端,開啟如下配置即可:

# 啟動和關閉註冊的服務隔離(基於Group黑/白名單的策略)。缺失則預設為false
spring.application.strategy.register.isolation.enabled=true

黑/白名單通過如下方式配置

spring.application.strategy.register.isolation.group.blacklist=
spring.application.strategy.register.isolation.group.whitelist=

消費端服務隔離

基於Group是否相同的策略,即消費端拿到的提供端列表,兩者的Group必須相同。只需要在閘道器或者服務端,開啟如下配置即可:

# 啟動和關閉消費端的服務隔離(基於Group是否相同的策略)。缺失則預設為false
spring.application.strategy.consumer.isolation.enabled=true

通過修改discovery-guide-service-b的Group名為其它名稱,執行Postman呼叫,將發現從discovery-guide-service-a無法拿到discovery-guide-service-b的任何例項。意味著在discovery-guide-service-a消費端進行了隔離

提供端服務隔離

基於Group是否相同的策略,即服務端被消費端呼叫,兩者的Group必須相同,否則拒絕呼叫,異構系統可以通過Header方式傳遞n-d-service-group值進行匹配。只需要在服務端(不適用閘道器),開啟如下配置即可:

# 啟動和關閉提供端的服務隔離(基於Group是否相同的策略)。缺失則預設為false
spring.application.strategy.provider.isolation.enabled=true

# 灰度路由策略的時候,需要指定對業務RestController類的掃描路徑。此項配置作用於RPC方式的呼叫攔截和消費端的服務隔離兩項工作
spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service.feign

在Postman呼叫,執行http://localhost:4001/invoke/abc,去呼叫discovery-guide-service-b服務,將出現如下異常。意味著在discovery-guide-service-b提供端進行了隔離

Reject to invoke because of isolation with different service group

Alt text 如果加上n-d-service-group=discovery-guide-group的Header,那麼兩者保持Group相同,則呼叫通過。這是解決異構系統呼叫微服務被隔離的一種手段 Alt text

基於Sentinel的全鏈路服務限流熔斷降級許可權和灰度融合

通過整合Sentinel,在服務端實現該功能

封裝NacosDataSource和ApolloDataSource,支援Nacos和Apollo兩個遠端配置中心,零程式碼實現Sentinel功能。更多的遠端配置中心,請參照Sentinel官方的DataSource並自行整合

1. Nacos的Key格式:Group為元資料中配置的[組名],Data Id為[服務名]-[規則型別]
2. Apollo的Key格式:[組名]-[服務名]-[規則型別]

支援遠端配置中心和本地規則檔案的讀取邏輯,即優先讀取遠端配置,如果不存在或者規則錯誤,則讀取本地規則檔案。動態實現遠端配置中心對於規則的熱重新整理

支援如下開關開啟該動能,預設是關閉的

# 啟動和關閉Sentinel限流降級熔斷許可權等功能。缺失則預設為false
spring.application.strategy.sentinel.enabled=true

原生Sentinel註解

參照下面程式碼,為介面方法增加@SentinelResource註解,value為sentinel-resource,blockHandler和fallback是防護其作用後需要執行的方法

@RestController
@ConditionalOnProperty(name = DiscoveryConstant.SPRING_APPLICATION_NAME, havingValue = "discovery-guide-service-b")
public class BFeignImpl extends AbstractFeignImpl implements BFeign {
    private static final Logger LOG = LoggerFactory.getLogger(BFeignImpl.class);

    @Override
    @SentinelResource(value = "sentinel-resource", blockHandler = "handleBlock", fallback = "handleFallback")
    public String invoke(@PathVariable(value = "value") String value) {
        value = doInvoke(value);

        LOG.info("呼叫路徑:{}", value);

        return value;
    }

    public String handleBlock(String value, BlockException e) {
        return value + "-> B server sentinel block, cause=" + e.getClass().getName() + ", rule=" + e.getRule() + ", limitApp=" + e.getRuleLimitApp();
    }

    public String handleFallback(String value) {
        return value + "-> B server sentinel fallback";
    }
}

原生Sentinel規則

原生Sentinel規則的用法,請參照Sentinel官方文件

流控規則

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-flow,規則內容如下:

[
    {
        "resource": "sentinel-resource",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "refResource": null,
        "controlBehavior": 0,
        "warmUpPeriodSec": 10,
        "maxQueueingTimeMs": 500,
        "clusterMode": false,
        "clusterConfig": null
    }
]

Alt text

降級規則

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-degrade,規則內容如下:

[
    {
        "resource": "sentinel-resource",
        "limitApp": "default",
        "count": 2,
        "timeWindow": 10,
        "grade": 0,
        "passCount": 0
    }
]

Alt text

授權規則

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-authority,規則內容如下:

[
    {
        "resource": "sentinel-resource",
        "limitApp": "discovery-guide-service-a",
        "strategy": 0
    }
]

Alt text

系統規則

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-system,規則內容如下:

[
    {
        "resource": null,
        "limitApp": null,
        "highestSystemLoad": -1.0,
        "highestCpuUsage": -1.0,
        "qps": 200.0,
        "avgRt": -1,
        "maxThread": -1
    }
]

Alt text

熱點引數流控規則

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-param-flow,規則內容如下:

[
    {
        "resource": "sentinel-resource",
        "limitApp": "default",
        "grade": 1,
        "paramIdx": 0,
        "count": 1,
        "controlBehavior": 0,
        "maxQueueingTimeMs": 0,
        "burstCount": 0,
        "durationInSec": 1,
        "paramFlowItemList": [],
        "clusterMode": false
    }
]

Alt text

基於灰度路由和Sentinel-LimitApp擴充套件的防護機制

該方式對於上面5種規則都有效,這裡以授權規則展開闡述

授權規則中,limitApp,如果有多個,可以通過“,”分隔。"strategy": 0 表示白名單,"strategy": 1 表示黑名單

基於服務名的防護機制

修改配置項Sentinel Request Origin Key為服務名的Header名稱,修改授權規則中limitApp為對應的服務名,可實現基於服務名的防護機制

配置項,該配置項預設為n-d-service-id,可以不配置

spring.application.strategy.service.sentinel.request.origin.key=n-d-service-id

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-authority,規則內容如下,表示所有discovery-guide-service-a服務允許訪問discovery-guide-service-b服務

[
    {
        "resource": "sentinel-resource",
        "limitApp": "discovery-guide-service-a",
        "strategy": 0
    }
]

基於灰度組的防護機制

修改配置項Sentinel Request Origin Key為灰度組的Header名稱,修改授權規則中limitApp為對應的組名,可實現基於組名的防護機制

配置項

spring.application.strategy.service.sentinel.request.origin.key=n-d-service-group

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-authority,規則內容如下,表示隸屬my-group組的所有服務都允許訪問服務discovery-guide-service-b

[
    {
        "resource": "sentinel-resource",
        "limitApp": "my-group",
        "strategy": 0
    }
]

基於灰度版本的防護機制

修改配置項Sentinel Request Origin Key為灰度版本的Header名稱,修改授權規則中limitApp為對應的版本,可實現基於版本的防護機制

配置項

spring.application.strategy.service.sentinel.request.origin.key=n-d-service-version

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-authority,規則內容如下,表示版本為1.0的所有服務都允許訪問服務discovery-guide-service-b

[
    {
        "resource": "sentinel-resource",
        "limitApp": "1.0",
        "strategy": 0
    }
]

基於灰度區域的防護機制

修改配置項Sentinel Request Origin Key為灰度區域的Header名稱,修改授權規則中limitApp為對應的區域,可實現基於區域的防護機制

配置項

spring.application.strategy.service.sentinel.request.origin.key=n-d-service-region

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-authority,規則內容如下,表示區域為dev的所有服務都允許訪問服務discovery-guide-service-b

[
    {
        "resource": "sentinel-resource",
        "limitApp": "dev",
        "strategy": 0
    }
]

基於機器地址和埠的防護機制

修改配置項Sentinel Request Origin Key為灰度區域的Header名稱,修改授權規則中limitApp為對應的區域值,可實現基於機器地址和埠的防護機制

配置項

spring.application.strategy.service.sentinel.request.origin.key=n-d-service-address

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-authority,規則內容如下,表示地址和埠為192.168.0.88:8081和192.168.0.88:8082的服務都允許訪問服務discovery-guide-service-b

[
    {
        "resource": "sentinel-resource",
        "limitApp": "192.168.0.88:8081,192.168.0.88:8082",
        "strategy": 0
    }
]

自定義業務引數的組合式防護機制

通過適配類實現自定義業務引數的組合式防護機制

// 自定義版本號+使用者名稱,實現組合式熔斷
public class MyServiceSentinelRequestOriginAdapter extends DefaultServiceSentinelRequestOriginAdapter {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String version = request.getHeader(DiscoveryConstant.N_D_SERVICE_VERSION);
        String user = request.getHeader("user");

        return version + "&" + user;
    }
}

在配置類裡@Bean方式進行適配類建立

@Bean
public ServiceSentinelRequestOriginAdapter ServiceSentinelRequestOriginAdapter() {
    return new MyServiceSentinelRequestOriginAdapter();
}

增加服務discovery-guide-service-b的規則,Group為discovery-guide-group,Data Id為discovery-guide-service-b-sentinel-authority,規則內容如下,表示版本為1.0且傳入的Http Header的user=zhangsan,同時滿足這兩個條件下的所有服務都允許訪問服務discovery-guide-service-b

[
    {
        "resource": "sentinel-resource",
        "limitApp": "1.0&zhangsan",
        "strategy": 0
    }
]

執行效果

  • 當傳遞的Http Header中user=zhangsan,當全鏈路呼叫中,API閘道器負載均衡discovery-guide-service-a服務到1.0版本後再去呼叫discovery-guide-service-b服務,最終呼叫成功

Alt text

  • 當傳遞的Http Header中user=lisi,不滿足條件,最終呼叫在discovery-guide-service-b服務端被拒絕掉

Alt text

  • 當傳遞的Http Header中user=zhangsan,滿足條件之一,當全鏈路呼叫中,API閘道器負載均衡discovery-guide-service-a服務到1.1版本後再去呼叫discovery-guide-service-b服務,不滿足version=1.0的條件,最終呼叫在discovery-guide-service-b服務端被拒絕掉

Alt text

基於Hystrix的全鏈路服務限流熔斷和灰度融合

通過引入Hystrix元件實現服務限流熔斷的功能,在執行灰度釋出和路由時候,執行緒池隔離模式下進行呼叫會丟失上下文,那麼需要下述步驟避免該情況。下面步驟同時適用於閘道器端和服務端

  • Pom引入
<!-- 當服務用Hystrix做執行緒隔離的時候,才需要匯入下面的包 -->
<dependency>
    <groupId>com.nepxion</groupId>
    <artifactId>discovery-plugin-strategy-starter-hystrix</artifactId>
    <version>${discovery.version}</version>
</dependency>
  • 配置開啟
# 開啟服務端實現Hystrix執行緒隔離模式做服務隔離時,必須把spring.application.strategy.hystrix.threadlocal.supported設定為true,同時要引入discovery-plugin-strategy-starter-hystrix包,否則執行緒切換時會發生ThreadLocal上下文物件丟失。缺失則預設為false
spring.application.strategy.hystrix.threadlocal.supported=true

全鏈路灰度呼叫鏈

灰度呼叫鏈主要包括如下6個引數。使用者可以自行定義要傳遞的呼叫鏈引數,例如:traceId, spanId等;也可以自行定義要傳遞的業務呼叫鏈引數,例如:mobile, user等

1. n-d-service-group - 服務所屬組或者應用
2. n-d-service-type - 服務型別,分為“閘道器”和“服務”
3. n-d-service-id - 服務ID
4. n-d-service-address - 服務地址,包括Host和Port
5. n-d-service-version - 服務版本
6. n-d-service-region - 服務所屬區域

灰度呼叫鏈輸出分為Header方式和日誌方式

Header輸出方式

Header方式框架內部整合

  • Spring Clou