springcloud gateway实现简易版灰度路由步骤详解

 更新时间:2023年11月22日 10:08:23   作者:linyb极客之路  
这篇文章主要为大家介绍了springcloud gateway实现简易版灰度路由步骤详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

前阵子时间和朋友聊天,他们有个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灰度路由的资料请关注脚本之家其它相关文章!

相关文章

  • Java中static关键字的作用解析

    Java中static关键字的作用解析

    这篇文章主要介绍了Java中static关键字的作用解析,Java 中,不能在所有类之外定义全局变量,只能通过在一个类中定义公用、静态的变量来实现一个全局变量,需要的朋友可以参考下
    2023-11-11
  • Java多线程之多种锁和阻塞队列

    Java多线程之多种锁和阻塞队列

    今天带大家学习的是Java多线程的相关知识,文章围绕着java多种锁和阻塞队列展开,文中有非常详细的介绍,需要的朋友可以参考下
    2021-06-06
  • Java Spring的数据库开发详解

    Java Spring的数据库开发详解

    这篇文章主要介绍了Spring的数据库开发,主要围绕SpringJDBC和Spring Jdbc Template两个技术来讲解,文中有详细的代码示例,需要的小伙伴可以参考一下
    2023-04-04
  • 基于Java实现空间滤波完整代码

    基于Java实现空间滤波完整代码

    空间滤波是一种采用滤波处理的影像增强方法。其理论基础是空间卷积和空间相关。这篇文章主要介绍了基于Java的空间滤波代码实现,需要的朋友可以参考下
    2021-08-08
  • Java连接MYSQL数据库的实现步骤

    Java连接MYSQL数据库的实现步骤

    以下的文章主要描述的是java连接MYSQL数据库的正确操作步骤,在此篇文章里我们主要是以实例列举的方式来引出其具体介绍
    2013-06-06
  • 简单了解java中静态初始化块的执行顺序

    简单了解java中静态初始化块的执行顺序

    这篇文章主要介绍了简单了解java中静态初始化块的执行顺序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Spring boot框架JWT实现用户账户密码登录验证流程

    Spring boot框架JWT实现用户账户密码登录验证流程

    这篇文章主要介绍了Springboot框架JWT实现用户账户密码登录验证,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • Java中string和int的互相转换问题

    Java中string和int的互相转换问题

    本文通过实例代码给大家详细介绍了Java中string和int的互相转换问题,感兴趣的朋友一起看看吧
    2017-10-10
  • Jenkins安装以及邮件配置详解

    Jenkins安装以及邮件配置详解

    这篇文章主要介绍了Jenkins安装以及邮件配置相关问题,并通过图文给大家做了详细讲解步骤,需要的朋友参考下吧。
    2017-12-12
  • SpringBoot项目整合拦截器详解

    SpringBoot项目整合拦截器详解

    这篇文章主要介绍了SpringBoot项目整合拦截器详解,java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,拦截器用于在某个方法或者字段被访问之前进行拦截,然后再之前或者之后加入某些操作,需要的朋友可以参考下
    2023-10-10

最新评论