1. 程式人生 > 程式設計 >SpringCloud 實戰之 Spring Cloud Gateway 金絲雀 灰度釋出

SpringCloud 實戰之 Spring Cloud Gateway 金絲雀 灰度釋出

本篇將對動態路由進行更深層的實現,實現思路如下:
  • 1.建立一個路由資訊維護的專案(dynamic-route),實現增刪改查路由資訊到mysql
  • 2.提供釋出功能,釋出後將路由資訊與版本資訊儲存到redis中,對外提供 rest 介面獲取路由資訊
  • 3.閘道器(gateway-dynamic-route)開啟定時任務,定時拉取 rest 介面中釋出的最新版的路由資訊,對比版本號,如果閘道器的版本號與rest介面中的不一致,則獲取路由資訊後更新閘道器路由,這樣閘道器釋出多個例項後,都會單獨的去拉取維護的路由資訊
  • 4.整體架構設計如下:
    在這裡插入圖片描述

根據上面的思路進行程式碼實現,下面將建立2個專案,都要註冊到eureka上

  • dynamic-route,路由專案
  • gateway-dynamic-route,閘道器,預設不配置路由資訊(不轉發到consumer-service)
  • consumer-service,消費者服務,動態路由配置後將請求轉發到此服務
2.建立一個路由資訊維護的專案 dynamic-route,實現對路由資訊的增刪改查和釋出功能,資訊儲存到mysql中,釋出後儲存到redis,對外提供路由資料時返回redis中的路由資訊(使用快取來應對閘道器定時任務讀請求)
  • 有2張表,路由資訊表與版本釋出表,表結構如下:
    在這裡插入圖片描述
  • 增刪改查的實現因篇幅原因就不一一展開了,可檢視專案程式碼,已上傳到碼雲,下面貼出專案的幾個頁面,頁面比較簡單
    在這裡插入圖片描述

    在這裡插入圖片描述
    下面是提供的 rest 介面返回版本資訊與路由資訊,閘道器先獲取版本號,與本地版本號對比,如不一致則通過此介面拉取路由資訊
    在這裡插入圖片描述
    獲取維護的路由資訊
    在這裡插入圖片描述
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,且釋出路由
    在這裡插入圖片描述
    釋出路由時,會將版本表最新的版本號與路由資訊新增到redis中,下圖是redis中儲存的路由資訊,閘道器定時獲取版本資訊與路由資訊都是先從redis中取出來的
    在這裡插入圖片描述

  • 6.因閘道器沒有配置路由資訊,可通過 localhost:9999/route/routes 獲取所有路由資訊,此時有兩條路由資訊,是閘道器從eureka上拉下來的預設的路由資訊,不是配置的路由資訊
    在這裡插入圖片描述

  • 7.等待60秒後,閘道器通過 RestTemplate 拉取dynamic-route專案的最新版本號,發現不一致,則拉取最新路由資訊,並更新到閘道器路由中
    在這裡插入圖片描述

  • 8.通過閘道器訪問 consumer-service,驗證路由資訊是否已更新到閘道器中,訪問:http://localhost:9999/zy/hello?name=zy
    在這裡插入圖片描述

  • 9.再次檢視所有路由資訊,可看到consumer-service已被設定到閘道器路由中了
    在這裡插入圖片描述

  • 10.每隔60秒再次對比版本號,當有新的版本號釋出後,就會拉取維護的路由資訊,沒有則閘道器不會更新路由
    在這裡插入圖片描述

  • 11.修改一下路由資訊,把斷言Path=/zy/** , 改為Path=/consumer/** ,併發布,隔了60秒後,閘道器的控制檯拉取到最新的路由資訊
    在這裡插入圖片描述
    此時訪問 http://localhost:9999/zy/hello?name=zy 報錯,因為路由斷言path被更改了路由不到
    在這裡插入圖片描述
    需要通過 http://localhost:9999/consumer/hello?name=zy 訪問,正常返回
    在這裡插入圖片描述
    好了,動態路由的進階實現已完成了,因篇幅原因沒有全部貼出所有程式碼,部落格主要介紹動態路由的實現思路,程式碼可根據自己的習慣進行實現

程式碼已上傳至碼雲:

專案版本資訊如下:

- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2複製程式碼

實現灰度釋出

新增2個路由資訊,相同的 "pattern": "/cm1/**",指向不同的服務,訪問



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","args": {
            "_genkey_0": "/order-s/**"
          }
        }
      ],"uri": "lb://order-service",{
    "route_id": "test-service","route_definition": {
      "id": "test-service","args": {
            "_genkey_0": "/demo/**"
          }
        }
      ],"uri": "lb://test","order": 0
  }
]
複製程式碼