Nepxion Discovery【探索】使用指南,基於Spring Cloud Greenwich版、Finchley版和Hoxton版而製做,對於Edgware版,使用者須要自行修改。使用指南主要涉及的功能包括:前端
[Nacos] 阿里巴巴中間件部門開發的新一代集服務註冊發現中心和配置中心爲一體的中間件。它是構建以「服務」爲中心的現代應用架構 (例如微服務範式、雲原生範式) 的服務基礎設施,支持幾乎全部主流類型的「服務」的發現、配置和管理,更敏捷和容易地構建、交付和管理微服務平臺java
[Sentinel] 阿里巴巴中間件部門開發的新一代以流量爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性的分佈式系統的流量防衛兵。它承接了阿里巴巴近10年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量能夠承受的範圍)、消息削峯填谷、集羣流量控制、實時熔斷下游不可用應用等git
[Spring Cloud Alibaba] 阿里巴巴中間件部門開發的Spring Cloud加強套件,致力於提供微服務開發的一站式解決方案。此項目包含開發分佈式應用微服務的必需組件,方便開發者經過Spring Cloud編程模型輕鬆使用這些組件來開發分佈式應用服務。依託Spring Cloud Alibaba,只須要添加一些註解和少許配置,就能夠將Spring Cloud應用接入阿里微服務解決方案,經過阿里中間件來迅速搭建分佈式應用系統github
示例以Nacos爲服務註冊中心和配置中心(使用者可自行換成其它服務註冊中心和配置中心),集成Spring Cloud Alibaba,經過Gateway和Zuul調用兩個版本或者區域的服務,模擬網關灰度路由和服務灰度權重的功能spring
若是使用者須要更強大的功能,請參考源碼主頁。規則策略不少,請使用者選擇最適合本身業務場景的方式docker
微信、公衆號和文檔數據庫
源碼主頁編程
指南主頁json
文檔主頁後端
類名 | 微服務 | 服務端口 | 版本 | 區域 |
---|---|---|---|---|
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的測試腳本,腳本地址
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]
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]
在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>
每一個服務調用的版本均可以自行指定,見下面第二條。當全部服務都選同一版本的時候,能夠簡化成下面第一條
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>
每一個服務調用的版本權重均可以自行指定,見下面第二條。當全部服務都選相同版本權重的時候,能夠簡化成下面第一條
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>
每一個服務調用的區域均可以自行指定,見下面第二條。當全部服務都選同一區域的時候,能夠簡化成下面第一條
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>
每一個服務調用的區域權重均可以自行指定,見下面第二條。當全部服務都選相同區域權重的時候,能夠簡化成下面第一條
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)方式傳入灰度路由策略,來代替配置中心方式,傳遞全鏈路路由策略。注意:當配置中心和界面都配置後,以界面傳入優先
1. n-d-version=1.0 2. n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
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"}
1. n-d-region=qa 2. n-d-region={"discovery-guide-service-a":"qa", "discovery-guide-service-b":"qa"}
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"}
n-d-address={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"}
當外界傳值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>
繼承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>
增長區域匹配的灰度規則,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>
增長全局版本權重的灰度規則,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>
增長局部版本權重的灰度規則,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>
增長全局區域權重的灰度規則,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>
增長局部區域權重的灰度規則,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>
注意:局部權重優先級高於全局權重,版本權重優先級高於區域權重
請執行Postman操做,請仔細觀察服務被隨機權重調用到的機率
增長組合式的灰度規則,Group爲discovery-guide-group,Data Id爲discovery-guide-group(全局發佈,二者都是組名),規則內容以下,實現功能:
該功能的意義是,網關隨機權重調用服務,而服務鏈路按照版本匹配方式調用
<?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>
圖形化界面驗證
元數據中的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
若是加上n-d-service-group=discovery-guide-group的Header,那麼二者保持Group相同,則調用經過。這是解決異構系統調用微服務被隔離的一種手段
經過集成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
參照下面代碼,爲接口方法增長@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官方文檔
增長服務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 } ]
增長服務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 } ]
增長服務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 } ]
增長服務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 } ]
增長服務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 } ]
該方式對於上面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 } ]
運行效果
經過引入Hystrix組件實現服務限流熔斷的功能,在執行灰度發佈和路由時候,線程池隔離模式下進行調用會丟失上下文,那麼須要下述步驟避免該狀況。下面步驟同時適用於網關端和服務端
<!-- 當服務用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方式框架內部集成
繼承GatewayStrategyTracer.java,trace方法裏把6個參數(參考父類裏debugTraceHeader方法)或者更多經過MDC方式輸出到日誌
// 自定義調用鏈和灰度調用鏈經過MDC輸出到日誌。使用者集成時候,關注trace方法中的MDC.put和release方法中MDC.clear代碼部分便可 public class MyGatewayStrategyTracer extends DefaultGatewayStrategyTracer { @Override public void trace(ServerWebExchange exchange) { super.trace(exchange); // 輸出到日誌 MDC.put("traceid", "traceid=" + strategyContextHolder.getHeader("traceid")); ... MDC.put(DiscoveryConstant.N_D_SERVICE_GROUP, "服務組名=" + strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_GROUP)); ... } @Override public void release(ServerWebExchange exchange) { MDC.clear(); } }
在配置類裏@Bean方式進行調用鏈類建立,覆蓋框架內置的調用鏈類
@Bean @ConditionalOnProperty(value = StrategyConstant.SPRING_APPLICATION_STRATEGY_TRACE_ENABLED, matchIfMissing = false) public GatewayStrategyTracer gatewayStrategyTracer() { return new MyGatewayStrategyTracer(); }
繼承ZuulStrategyTracer.java,trace方法裏把6個參數(參考父類裏debugTraceHeader方法)或者更多經過MDC方式輸出到日誌
// 自定義調用鏈和灰度調用鏈經過MDC輸出到日誌。使用者集成時候,關注trace方法中的MDC.put和release方法中MDC.clear代碼部分便可 public class MyZuulStrategyTracer extends DefaultZuulStrategyTracer { @Override public void trace(RequestContext context) { super.trace(context); // 輸出到日誌 MDC.put("traceid", "traceid=" + strategyContextHolder.getHeader("traceid")); ... MDC.put(DiscoveryConstant.N_D_SERVICE_GROUP, "服務組名=" + strategyContextHolder.getHeader(DiscoveryConstant.N_D_SERVICE_GROUP)); ... } @Override public void release(RequestContext context) { MDC.clear(); } }
在配置類裏@Bean方式進行調用鏈類建立,覆蓋框架內置的調用鏈類
@Bean @ConditionalOnProperty(value = StrategyConstant.SPRING_APPLICATION_STRATEGY_TRACE_ENABLED, matchIfMissing = false) public ZuulStrategyTracer zuulStrategyTracer() { return new MyZuulStrategyTracer(); }
繼承ServiceStrategyTracer.java,trace方法裏把6個參數(參考父類裏debugTraceLocal方法)或者更多經過MDC方式輸出到日誌
// 自定義調用鏈和灰度調用鏈經過MDC輸出到日誌。使用者集成時候,關注trace方法中的MDC.put和release方法中MDC.clear代碼部分便可 public class MyServiceStrategyTracer extends DefaultServiceStrategyTracer { @Override public void trace(ServiceStrategyTracerInterceptor interceptor, MethodInvocation invocation) { super.trace(interceptor, invocation); // 輸出到日誌 MDC.put("traceid", "traceid=" + strategyContextHolder.getHeader("traceid")); ... MDC.put(DiscoveryConstant.N_D_SERVICE_GROUP, "服務組名=" + pluginAdapter.getGroup()); ... } @Override public void release(ServiceStrategyTracerInterceptor interceptor, MethodInvocation invocation) { MDC.clear(); } }
在配置類裏@Bean方式進行調用鏈類建立,覆蓋框架內置的調用鏈類
@Bean @ConditionalOnProperty(value = StrategyConstant.SPRING_APPLICATION_STRATEGY_TRACE_ENABLED, matchIfMissing = false) public ServiceStrategyTracer serviceStrategyTracer() { return new MyServiceStrategyTracer(); }
請參考在IDE控制檯打印的結果
對於調用鏈功能的開啓和關閉,須要經過以下開關作控制:
# 啓動和關閉調用鏈。缺失則默認爲false spring.application.strategy.trace.enabled=true # 啓動和關閉調用鏈的Debug日誌打印,注意每調用一次都會打印一次,會對性能有所影響,建議壓測環境和生產環境關閉。缺失則默認爲false spring.application.strategy.trace.debug.enabled=true
框架會默認把相關的Header,進行全鏈路傳遞,能夠經過以下配置進行。除此以外,凡是以「n-d-」開頭的任何Header,框架都會默認全鏈路傳遞
# 啓動和關閉路由策略的時候,對REST方式的調用攔截。缺失則默認爲true spring.application.strategy.rest.intercept.enabled=true # 啓動和關閉Header傳遞的Debug日誌打印,注意每調用一次都會打印一次,會對性能有所影響,建議壓測環境和生產環境關閉。缺失則默認爲false spring.application.strategy.rest.intercept.debug.enabled=true # 灰度路由策略的時候,對REST方式調用攔截的時候(支持Feign或者RestTemplate調用),但願把來自外部自定義的Header參數(用於框架內置上下文Header,例如:traceid, spanid等)傳遞到服務裏,那麼配置以下值。若是多個用「;」分隔,不容許出現空格 spring.application.strategy.context.request.headers=traceid;spanid # 灰度路由策略的時候,對REST方式調用攔截的時候(支持Feign或者RestTemplate調用),但願把來自外部自定義的Header參數(用於業務系統子定義Header,例如:mobile)傳遞到服務裏,那麼配置以下值。若是多個用「;」分隔,不容許出現空格 spring.application.strategy.business.request.headers=user;mobile
原生的Feign Header傳遞可使用RequestInterceptor攔截器實現,原生的RestTemplate Header傳遞可使用ClientHttpRequestInterceptor攔截器實現
本框架也使用這些原生的攔截器用做Header在灰度功能上的傳遞,爲了不使用者再去多建立一層攔截器,框架抽象出兩個攔截適配器,用法和原生的兩個攔截器一致,能夠幫助使用者實現自定義Header的傳遞
實現FeignStrategyInterceptorAdapter.java,在apply方法里加入自定義的Header傳遞
// 自定義Feign攔截器中的Header傳遞 public class MyFeignStrategyInterceptorAdapter extends DefaultFeignStrategyInterceptorAdapter { @Override public void apply(RequestTemplate requestTemplate) { requestTemplate.header("n-d-my-id", "123"); } }
在配置類裏@Bean方式進行攔截適配器建立
@Bean public FeignStrategyInterceptorAdapter feignStrategyInterceptorAdapter() { return new MyFeignStrategyInterceptorAdapter(); }
實現RestTemplateStrategyInterceptorAdapter.java,在intercept方法里加入自定義的Header傳遞
// 自定義RestTemplate攔截器中的Header傳遞 public class MyRestTemplateStrategyInterceptorAdapter extends DefaultRestTemplateStrategyInterceptorAdapter { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpHeaders headers = request.getHeaders(); headers.add("n-d-my-id", "456"); return execution.execute(request, body); } }
在配置類裏@Bean方式進行攔截適配器建立
@Bean public RestTemplateStrategyInterceptorAdapter restTemplateStrategyInterceptorAdapter() { return new MyRestTemplateStrategyInterceptorAdapter(); }
請自行研究