springcloud?gateway實(shí)現(xiàn)簡易版灰度路由步驟詳解
前言
前陣子時(shí)間和朋友聊天,他們有個(gè)sass微服務(wù),因?yàn)橹安鸱诌^細(xì),導(dǎo)致服務(wù)不僅調(diào)用鏈路過長,而且浪費(fèi)服務(wù)資源,他們后面做了服務(wù)合并的重構(gòu),并即將上線。他覺得上線不能直接把線上的租戶都全切到重構(gòu)版的sass微服務(wù),而是需要實(shí)現(xiàn)如下的效果
他就問我說,有沒有啥開源平臺(tái)可以快速支持,因?yàn)橹皶r(shí)間都耗費(fèi)在重構(gòu)業(yè)務(wù)上,這塊就沒考慮周全,現(xiàn)在臨近上線,預(yù)留的時(shí)間不多。后面和他細(xì)聊,得知他們這套sass服務(wù),租戶不多,其次他們微服務(wù)API網(wǎng)關(guān)是springcloud gateway。了解到這個(gè)信息后,我就跟他說直接拿API網(wǎng)關(guān)稍微改造一下,就可以達(dá)到他目前想要的效果。下面就來聊聊如何利用springcloud gateway實(shí)現(xiàn)簡易版灰度路由
實(shí)現(xiàn)關(guān)鍵
?springcloud gateway 自定義斷言工廠 + 開啟服務(wù)發(fā)現(xiàn)路由定位器 + PropertiesRouteDefinitionLocator 生成的route與DiscoveryClientRouteDefinitionLocator生成route path映射保持一致
實(shí)現(xiàn)步驟
注: 本示例注冊(cè)中心使用eureka,其他注冊(cè)中心也可以
1、項(xiàng)目POM引入相關(guān)GAV
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2、自定義斷言工廠
@Slf4j public class ParamRoutePredicateFactory extends AbstractRoutePredicateFactory<ParamRoutePredicateFactory.Config> { public static final String PARAM_KEY = "param"; public static final String PARAM_VALUES = "values"; public static final String SEPARATOR = "&"; public ParamRoutePredicateFactory() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList(PARAM_KEY,PARAM_VALUES); } @Override public ShortcutType shortcutType() { return ShortcutType.DEFAULT; } @Override public Predicate<ServerWebExchange> apply(Config config) { return exchange -> isHitTargetParam(config, exchange); } private boolean isHitTargetParam(Config config, ServerWebExchange exchange) { boolean hasParamkey = HttpRequestParserUtils.hasKey(config.param.toLowerCase(), exchange); if(hasParamkey){ String value = HttpRequestParserUtils.parse(config.param.toLowerCase(), exchange); if(StringUtils.hasText(config.values) && config.values.contains(SEPARATOR)){ String[] valueArr = config.values.split(SEPARATOR); for (String targetValue : valueArr) { if(targetValue.equals(value)){ log.info(">>>>>>>>>>>>>>>>>>>> Request Key --> 【{}】 Hit Value --> 【{}】 In Target Values 【{}】", config.param,value, config.values); return true; } } } } return false; } @Validated public static class Config { @NotEmpty private String param; private String values; public String getParam() { return param; } public Config setParam(String param) { this.param = param; return this; } public String getValues() { return values; } public Config setValues(String values) { this.values = values; return this; } @Override public String toString() { return "Config{" + "param='" + param + '\'' + ", values=" + values + '}'; } }
3、配置斷言工程自動(dòng)裝配
@Configuration @ConditionalOnProperty(name = "spring.cloud.gateway.ext.enabled", havingValue = "true",matchIfMissing = true) @AutoConfigureBefore({ GatewayDiscoveryClientAutoConfiguration.class}) @ConditionalOnClass(DispatcherHandler.class) public class GatewayAutoExtConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = "spring.cloud.gateway.properties-route-definition-locator.load.first", havingValue = "true",matchIfMissing = true) public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator( GatewayProperties properties) { return new PropertiesRouteDefinitionLocator(properties); } @Bean @ConditionalOnMissingBean public ParamRoutePredicateFactory paramRoutePredicateFactory(){ return new ParamRoutePredicateFactory(); } }
注: 這邊有些細(xì)節(jié)點(diǎn)說明一下,該配置先于GatewayDiscoveryClientAutoConfiguration裝配,主要是實(shí)現(xiàn)PropertiesRouteDefinitionLocator 比DiscoveryClientRouteDefinitionLocator優(yōu)先加載,為啥這么做,后面說
4、在application.yml文件開啟服務(wù)發(fā)現(xiàn)路由定位器
spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true
測試灰度路由
1、測試微服務(wù)comsumer1
a、測試配置
spring: application: name: ${APPLICATION_NAME:comsumer} profiles: active: eureka
b、編寫測試控制器
@RestController @RequestMapping("echo") public class EchoController { @GetMapping("{message}") public String echo(@PathVariable("message") String message){ System.out.println("comsumer:" + message); return "comsumer :" + message; } }
2、測試微服務(wù)comsumer2
a、測試配置
spring: application: name: ${APPLICATION_NAME:otherComsumer} profiles: active: eureka
b、編寫測試控制器
@RestController @RequestMapping("echo") public class EchoController { @GetMapping("{message}") public String echo(@PathVariable("message") String message){ System.out.println("otherComsumer:" + message); return "otherComsumer :" + message; } }
注:這個(gè)兩個(gè)服務(wù)主要用來模擬新老集群數(shù)據(jù)
3、網(wǎng)關(guān)添加測試路由配置
spring: cloud: gateway: routes: - id: route-springboot-gray-comsumer-to-other-comsumer uri: http://localhost:8083 predicates: - Path=/comsumer/** ## 多個(gè)租戶用&分割 - Param=tenantId,10000&10001&10002 filters: - StripPrefix=1 order: 0
注: 這個(gè)配置心細(xì)的朋友,可能會(huì)發(fā)現(xiàn)貓膩了。這個(gè)PATH和開啟服務(wù)發(fā)現(xiàn)路由定位器生成的PATH是一樣,我們?cè)賮碚f下為啥上面實(shí)現(xiàn)PropertiesRouteDefinitionLocator 比DiscoveryClientRouteDefinitionLocator優(yōu)先加載,因?yàn)槁酚啥ㄎ黄鳟a(chǎn)生的route是有順序性,而當(dāng)PropertiesRouteDefinitionLocator 和DiscoveryClientRouteDefinitionLocator配置的PATH一樣時(shí),如果DiscoveryClientRouteDefinitionLocator優(yōu)于PropertiesRouteDefinitionLocator加載,就會(huì)導(dǎo)致訪問相同路徑時(shí),會(huì)優(yōu)先訪問DiscoveryClientRouteDefinitionLocator生成的route,就不會(huì)去走我們自定義配置的route。不過這個(gè)結(jié)論為時(shí)尚早,留個(gè)懸念,待會(huì)說明
4、測試
1、當(dāng)我們請(qǐng)求頭、cookie、query不加tenantId參數(shù)或者tenantId不為測試10000&10001&10002的值時(shí)
2、當(dāng)tenantId滿足10000&10001&10002的其中任意值時(shí)
可以發(fā)現(xiàn)已經(jīng)路由到我們配置的地址
3、當(dāng)我們對(duì)網(wǎng)關(guān)做如下配置
spring: cloud: gateway: properties-route-definition-locator: load: first: false
該配置主要是為了讓我們自定義的PropertiesRouteDefinitionLocator 的BEAN失效,這樣他就會(huì)按默認(rèn)的加載邏輯,即DiscoveryClientRouteDefinitionLocator會(huì)先于PropertiesRouteDefinitionLocator 加載
同時(shí)路由做如下配置
spring: cloud: gateway: routes: - id: route-springboot-gray-comsumer-to-other-comsumer uri: http://localhost:8083 predicates: - Path=/comsumer/** ## 多個(gè)租戶用&分割 - Param=tenantId,10000&10001&10002 filters: - StripPrefix=1 order: -1000
即將order的數(shù)值調(diào)低。我們?cè)衮?yàn)證下
會(huì)發(fā)現(xiàn)效果和我們之前演示的效果是一樣的。其實(shí)這邊實(shí)現(xiàn)路由的關(guān)鍵點(diǎn),是抓住route的順序性,相同路徑,誰先加載,誰先路由。所以我實(shí)現(xiàn)PropertiesRouteDefinitionLocator 比DiscoveryClientRouteDefinitionLocator會(huì)優(yōu)先加載,就是為了實(shí)現(xiàn)當(dāng)path一樣時(shí),PropertiesRouteDefinitionLocator 生成的route都比DiscoveryClientRouteDefinitionLocator生成route優(yōu)先,當(dāng)然也可以通過配置order改變這個(gè)順序
總結(jié)
?本示例主要講解如何利用springcloud gateway實(shí)現(xiàn)簡易版灰度路由,不過該實(shí)現(xiàn)比較適用于灰度規(guī)則比較簡單的場景。如果需要復(fù)雜規(guī)則,就需要深層次的定制,或者采用用istio來實(shí)現(xiàn)也是一個(gè)挺好的選擇
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-gateway-simple-gray
以上就是springcloud gateway實(shí)現(xiàn)簡易版灰度路由步驟詳解的詳細(xì)內(nèi)容,更多關(guān)于spring cloud gateway灰度路由的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot中如何通過main方法調(diào)用service或dao
這篇文章主要介紹了springboot中如何通過main方法調(diào)用service或dao,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02SpringBoot2.x 整合 thumbnailator 圖片處理的示例代碼
這篇文章主要介紹了SpringBoot2.x 之整合 thumbnailator 圖片處理,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Spring boot調(diào)用Oracle存儲(chǔ)過程的兩種方式及完整代碼
這篇文章主要給大家介紹了關(guān)于Spring boot調(diào)用Oracle存儲(chǔ)過程的兩種方式及完整代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08JDK1.8下載、安裝和環(huán)境配置超詳細(xì)教程(最新最完整)
jdk1.8是一款功能強(qiáng)大的Java語音軟件開發(fā)工具包,JDK是學(xué)好Java的第一步,本文重點(diǎn)給大家介紹JDK1.8下載、安裝和環(huán)境配置教程,需要的朋友可以參考下2022-11-11Java數(shù)據(jù)長度獲取方式對(duì)比之length屬性、length()和size()方法詳解
在Java編程語言中l(wèi)ength、length()和size()是三個(gè)常見的用來獲取不同數(shù)據(jù)類型對(duì)象長度或大小的方法,但它們各自適用于不同的上下文,這篇文章主要給大家介紹了關(guān)于Java數(shù)據(jù)長度獲取方式對(duì)比之length屬性、length()和size()方法詳解2024-07-07