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 訪問,正常返回
好了,動態路由的進階實現已完成了,因篇幅原因沒有全部貼出所有程式碼,部落格主要介紹動態路由的實現思路,程式碼可根據自己的習慣進行實現
程式碼已上傳至碼雲:
- eureka-server
- dynamic-route,design中有建表sql
- gateway-dynamic-route
- consumer-service
專案版本資訊如下:
- 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
}
]
複製程式碼