springcloud gateway实现简易版灰度路由步骤详解
作者:linyb极客之路
前言
前阵子时间和朋友聊天,他们有个sass微服务,因为之前拆分过细,导致服务不仅调用链路过长,而且浪费服务资源,他们后面做了服务合并的重构,并即将上线。他觉得上线不能直接把线上的租户都全切到重构版的sass微服务,而是需要实现如下的效果
他就问我说,有没有啥开源平台可以快速支持,因为之前时间都耗费在重构业务上,这块就没考虑周全,现在临近上线,预留的时间不多。后面和他细聊,得知他们这套sass服务,租户不多,其次他们微服务API网关是springcloud gateway。了解到这个信息后,我就跟他说直接拿API网关稍微改造一下,就可以达到他目前想要的效果。下面就来聊聊如何利用springcloud gateway实现简易版灰度路由
实现关键
springcloud gateway 自定义断言工厂 + 开启服务发现路由定位器 + PropertiesRouteDefinitionLocator 生成的route与DiscoveryClientRouteDefinitionLocator生成route path映射保持一致
实现步骤
注: 本示例注册中心使用eureka,其他注册中心也可以
1、项目POM引入相关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、配置断言工程自动装配
@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(); } }
注: 这边有些细节点说明一下,该配置先于GatewayDiscoveryClientAutoConfiguration装配,主要是实现PropertiesRouteDefinitionLocator 比DiscoveryClientRouteDefinitionLocator优先加载,为啥这么做,后面说
4、在application.yml文件开启服务发现路由定位器
spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true
测试灰度路由
1、测试微服务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、测试微服务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; } }
注:这个两个服务主要用来模拟新老集群数据
3、网关添加测试路由配置
spring: cloud: gateway: routes: - id: route-springboot-gray-comsumer-to-other-comsumer uri: http://localhost:8083 predicates: - Path=/comsumer/** ## 多个租户用&分割 - Param=tenantId,10000&10001&10002 filters: - StripPrefix=1 order: 0
注: 这个配置心细的朋友,可能会发现猫腻了。这个PATH和开启服务发现路由定位器生成的PATH是一样,我们再来说下为啥上面实现PropertiesRouteDefinitionLocator 比DiscoveryClientRouteDefinitionLocator优先加载,因为路由定位器产生的route是有顺序性,而当PropertiesRouteDefinitionLocator 和DiscoveryClientRouteDefinitionLocator配置的PATH一样时,如果DiscoveryClientRouteDefinitionLocator优于PropertiesRouteDefinitionLocator加载,就会导致访问相同路径时,会优先访问DiscoveryClientRouteDefinitionLocator生成的route,就不会去走我们自定义配置的route。不过这个结论为时尚早,留个悬念,待会说明
4、测试
1、当我们请求头、cookie、query不加tenantId参数或者tenantId不为测试10000&10001&10002的值时
2、当tenantId满足10000&10001&10002的其中任意值时
可以发现已经路由到我们配置的地址
3、当我们对网关做如下配置
spring: cloud: gateway: properties-route-definition-locator: load: first: false
该配置主要是为了让我们自定义的PropertiesRouteDefinitionLocator 的BEAN失效,这样他就会按默认的加载逻辑,即DiscoveryClientRouteDefinitionLocator会先于PropertiesRouteDefinitionLocator 加载
同时路由做如下配置
spring: cloud: gateway: routes: - id: route-springboot-gray-comsumer-to-other-comsumer uri: http://localhost:8083 predicates: - Path=/comsumer/** ## 多个租户用&分割 - Param=tenantId,10000&10001&10002 filters: - StripPrefix=1 order: -1000
即将order的数值调低。我们再验证下
会发现效果和我们之前演示的效果是一样的。其实这边实现路由的关键点,是抓住route的顺序性,相同路径,谁先加载,谁先路由。所以我实现PropertiesRouteDefinitionLocator 比DiscoveryClientRouteDefinitionLocator会优先加载,就是为了实现当path一样时,PropertiesRouteDefinitionLocator 生成的route都比DiscoveryClientRouteDefinitionLocator生成route优先,当然也可以通过配置order改变这个顺序
总结
本示例主要讲解如何利用springcloud gateway实现简易版灰度路由,不过该实现比较适用于灰度规则比较简单的场景。如果需要复杂规则,就需要深层次的定制,或者采用用istio来实现也是一个挺好的选择
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-gateway-simple-gray
以上就是springcloud gateway实现简易版灰度路由步骤详解的详细内容,更多关于spring cloud gateway灰度路由的资料请关注脚本之家其它相关文章!