专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

SpringCloud 实战之 Spring Cloud Gateway 金丝雀 灰度发布

本篇将对动态路由进行更深层的实现,实现思路如下:
  • 1.创建一个路由信息维护的项目(dynamic-route),实现增删改查路由信息到mysql
  • 2.提供功能,后将路由信息与版本信息保存到redis中,对外提供 rest 接口获取路由信息
  • 3.网关(gateway-dynamic-route)开启定时任务,定时拉取 rest 接口中的最新版的路由信息,对比版本号,如果网关的版本号与rest接口中的不一致,则获取路由信息后更新网关路由,这样网关多个实例后,都会单独的去拉取维护的路由信息
  • 4.整体架构设计如下:
    110_1.png

根据上面的思路进行代码实现,下面将创建2个项目,都要注册到eureka上

  • dynamic-route,路由项目
  • gateway-dynamic-route,网关,默认不配置路由信息(不转发到consumer-service)
  • consumer-service,消费者服务,动态路由配置后将请求转发到此服务
2.创建一个路由信息维护的项目 dynamic-route,实现对路由信息的增删改查和功能,信息保存到mysql中,后保存到redis,对外提供路由数据时返回redis中的路由信息(使用缓存来应对网关定时任务读请求)
  • 有2张表,路由信息表与版本表,表结构如下:
    110_2.png
  • 增删改查的实现因篇幅原因就不一一展开了,可查看项目代码,已上传到码云,下面贴出项目的几个页面,页面比较简单
    110_3.png
    110_4.png
    下面是提供的 rest 接口返回版本信息与路由信息,网关先获取版本号,与本地版本号对比,如不一致则通过此接口拉取路由信息
    110_5.png
    获取维护的路由信息
    110_6.png
3.重点是网关项目,创建 gateway-dynamic-route 项目,网关启动时设置默认的版本号为0,通过定时任务每60秒拉取一次远程路由项目提供最新版本号,与网关的版本号对比,如不一致,则拉取远程路由项目最新的路由信息,更新到网关路由上去,同时把本地版本号覆盖为最新的版本号

因代码较多,博客主要分享实现思路和贴出关键代码,已提交至码云,可下载项目查看全部代码

  • 1.启动定时任务,注册到erueka,添加RestTemplate Bean且以负载均衡形式访问路由项目
@EnableScheduling
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayDynamicRouteApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayDynamicRouteApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

  • 2.定时任务实现类
/**
 * 定时任务,拉取路由信息
 * 路由信息由路由项目单独维护
 */
@Component
public class DynamicRouteScheduling {

    @Autowired private RestTemplate restTemplate;
    @Autowired private DynamicRouteService dynamicRouteService;//动态路由实现类,与前篇博客中的实现类代码是一样的

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final String dynamicRouteServerName = "dynamic-route-service";

    //路由信息的版本号
    private static Long versionId = 0L;

    //每60秒中执行一次
    //如果版本号不相等则获取最新路由信息并更新网关路由
    @Scheduled(cron = "*/60 * * * * ?")
    public void getDynamicRouteInfo(){
        try{
            System.out.println("拉取时间:" + dateFormat.format(new Date()));
            //先拉取版本信息,如果版本号不想等则更新路由
            Long resultVersionId = restTemplate.getForObject("http://"+ dynamicRouteServerName +"/version/lastVersion" , Long.class);
            System.out.println("路由版本信息:本地版本号:" + versionId + ",远程版本号:" + resultVersionId);
            if(resultVersionId != null && versionId != resultVersionId){
                System.out.println("开始拉取路由信息......");
                String resultRoutes = restTemplate.getForObject("http://"+ dynamicRouteServerName +"/gateway-routes/routes" , String.class);
                System.out.println("路由信息为:" + resultRoutes);
                if(!StringUtils.isEmpty(resultRoutes)){
                    List<GatewayRouteDefinition> list = JSON.parseArray(resultRoutes , GatewayRouteDefinition.class);
                    for(GatewayRouteDefinition definition : list){
                        //更新路由
                        RouteDefinition routeDefinition = assembleRouteDefinition(definition);
                        dynamicRouteService.update(routeDefinition);
                    }
                    versionId = resultVersionId;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //把前端传递的参数转换成路由对象
    private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(gwdefinition.getId());
        definition.setOrder(gwdefinition.getOrder());

        //设置断言
        List<PredicateDefinition> pdList=new ArrayList<>();
        List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
        for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setArgs(gpDefinition.getArgs());
            predicate.setName(gpDefinition.getName());
            pdList.add(predicate);
        }
        definition.setPredicates(pdList);

        //设置过滤器
        List<FilterDefinition> filters = new ArrayList();
        List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
        for(GatewayFilterDefinition filterDefinition : gatewayFilters){
            FilterDefinition filter = new FilterDefinition();
            filter.setName(filterDefinition.getName());
            filter.setArgs(filterDefinition.getArgs());
            filters.add(filter);
        }
        definition.setFilters(filters);

        URI uri = null;
        if(gwdefinition.getUri().startsWith("http")){
            uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
        }else{
            uri = URI.create(gwdefinition.getUri());
        }
        definition.setUri(uri);
        return definition;
    }
}

  • 3.网关提供获取所有路由信息的Controller
/**
 * 查询网关的路由信息
 */
@RestController
@RequestMapping("/route")
public class DynamicRouteController {

    @Autowired private RouteDefinitionLocator routeDefinitionLocator;

    //获取网关所有的路由信息
    @RequestMapping("/routes")
    public Flux<RouteDefinition> getRouteDefinitions(){
        return routeDefinitionLocator.getRouteDefinitions();
    }
}

  • 4.启动eureka、dynamic-route、gateway-dynamic-route、consumer-service
  • 5.通过 dynamic-route 项目新增一条路由信息,路由的 uri 是consumer-service,且路由
    110_7.png
    路由时,会将版本表最新的版本号与路由信息添加到redis中,下图是redis中保存的路由信息,网关定时获取版本信息与路由信息都是先从redis中取出来的
    110_8.png
  • 6.因网关没有配置路由信息,可通过 localhost:9999/route/routes 获取所有路由信息,此时有两条路由信息,是网关从eureka上拉下来的默认的路由信息,不是配置的路由信息
    110_9.png
  • 7.等待60秒后,网关通过 RestTemplate 拉取dynamic-route项目的最新版本号,发现不一致,则拉取最新路由信息,并更新到网关路由中
    110_10.png
  • 8.通过网关访问 consumer-service,验证路由信息是否已更新到网关中,访问:http://localhost:9999/zy/hello?name=zy
    110_11.png
  • 9.再次查看所有路由信息,可看到consumer-service已被设置到网关路由中了
    110_12.png
  • 10.每隔60秒再次对比版本号,当有新的版本号后,就会拉取维护的路由信息,没有则网关不会更新路由
    110_13.png
  • 11.修改一下路由信息,把断言Path=/zy/** , 改为Path=/consumer/** ,并,隔了60秒后,网关的控制台拉取到最新的路由信息
    110_14.png
    此时访问 http://localhost:9999/zy/hello?name=zy 报错,因为路由断言path被更改了路由不到
    110_15.png
    需要通过 http://localhost:9999/consumer/hello?name=zy 访问,正常返回
    110_16.png
    好了,动态路由的进阶实现已完成了,因篇幅原因没有全部贴出所有代码,博客主要介绍动态路由的实现思路,代码可根据自己的习惯进行实现

代码已上传至码云:

项目版本信息如下:

- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2

实现灰度

添加2个路由信息,相同的 “pattern”: “/cm1/**”,指向不同的服务,访问

110_17.png

110_18.png

http://localhost:9999/cm1/hello?name=alex

页面随机出现:hello alexfrom default,hello alexfrom b,说明在负载均衡访问2个服务,通过调节weight实现随权重

本文链接:blog.csdn.net/zhuyu199110…

yml转json

        - id: admin-service
          uri: lb://admin-service
          predicates:
          - Path=/admin/**
          - Weight=service1, 90
          filters:
          - SwaggerHeaderFilter
          - StripPrefix=1
        - id: order-service
          uri: lb://order-service
          predicates:
          - Path=/order-s/**
          filters:
          - SwaggerHeaderFilter
          - StripPrefix=1
        - id: test-service
          uri: lb://test
          predicates:
          - Path=/demo/**
          filters:
          - SwaggerHeaderFilter
          - StripPrefix=1

[
  {
    "route_id": "admin-service",
    "route_definition": {
      "id": "admin-service",
      "predicates": [
        {
          "name": "Path",
          "args": {
            "_genkey_0": "/admin/**"
          }
        },
        {
          "name": "Weight",
          "args": {
            "_genkey_0": "service1",
            "_genkey_1": "90"
          }
        }
      ],
      "filters": [
        {
          "name": "SwaggerHeaderFilter",
          "args": {}
        },
        {
          "name": "StripPrefix",
          "args": {
            "_genkey_0": "1"
          }
        }
      ],
      "uri": "lb://admin-service",
      "order": 0
    },
    "order": 0
  },
  {
    "route_id": "order-service",
    "route_definition": {
      "id": "order-service",
      "predicates": [
        {
          "name": "Path",
          "args": {
            "_genkey_0": "/order-s/**"
          }
        }
      ],
      "filters": [
        {
          "name": "SwaggerHeaderFilter",
          "args": {}
        },
        {
          "name": "StripPrefix",
          "args": {
            "_genkey_0": "1"
          }
        }
      ],
      "uri": "lb://order-service",
      "order": 0
    },
    "order": 0
  },
  {
    "route_id": "test-service",
    "route_definition": {
      "id": "test-service",
      "predicates": [
        {
          "name": "Path",
          "args": {
            "_genkey_0": "/demo/**"
          }
        }
      ],
      "filters": [
        {
          "name": "SwaggerHeaderFilter",
          "args": {}
        },
        {
          "name": "StripPrefix",
          "args": {
            "_genkey_0": "1"
          }
        }
      ],
      "uri": "lb://test",
      "order": 0
    },
    "order": 0
  }
]

文章永久链接:https://tech.souyunku.com/41652

未经允许不得转载:搜云库技术团队 » SpringCloud 实战之 Spring Cloud Gateway 金丝雀 灰度发布

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们