SpringCloud實戰十三:Gateway之 Spring Cloud Gateway 動態路由


前面分別對 Spring Cloud Zuul 與 Spring Cloud Gateway 進行了簡單的說明,它門是API網關,API網關負責服務請求路由、組合及協議轉換,客戶端的所有請求都首先經過API網關,然后由它將匹配的請求路由到合適的微服務,是系統流量的入口,在實際生產環境中為了保證高可靠和高可用,盡量避免重啟,如果有新的服務要上線時,可以通過動態路由配置功能上線。

本篇拿 Spring Cloud Gateway 為例,對網關的動態路由進行簡單分析,下一篇將分享動態路由的進階實現

1.gateway配置路由主要有兩種方式,1.用yml配置文件,2.寫在代碼里。而無論是 yml,還是代碼配置,啟動網關后將無法修改路由配置,如有新服務要上線,則需要先把網關下線,修改 yml 配置后,再重啟網關
  • yml配置方式
    在這里插入圖片描述
  • 代碼配置方式
    在這里插入圖片描述
2.gateway網關啟動時,路由信息默認會加載內存中,路由信息被封裝到 RouteDefinition 對象中,配置多個RouteDefinition組成gateway的路由系統,仔細的同學可能看到RouteDefinition中的字段與上面代碼配置方式比較對應
  • RouteDefinition對象在 org.springframework.cloud.gateway.route包下,其定義如下:
    在這里插入圖片描述
  • RouteDefinitionLocator是個接口,在org.springframework.cloud.gateway.route包下,如果想查看網關中所有的路由信息,調用此接口方法是一個辦法,需要從先注入到容器,后面還有另外一種查看方式,是Spring Cloud Gateway 的Endpoint端點提供的方法
    在這里插入圖片描述
3.Spring Cloud Gateway 提供了 Endpoint 端點,暴露路由信息,有獲取所有路由、刷新路由、查看單個路由、刪除路由等方法,源碼在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint 中,想訪問端點中的方法需要添加 spring-boot-starter-actuator 注解,並在配置文件中暴露所有端點
# 暴露端點
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
4.動態路由代碼實現

前提:需要啟動 3個服務,eureka、gateway、consumer-service

  • 1.eureka使用前面博客中的代碼
  • 2.consumer-service是個web項目,提供一個hello方法,需注冊到eureka上
  • 3.新建gateway,添加引用
<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>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 4.添加基本配置和注冊到eureka,不要配置路由信息映射到consumer-service,由后面的動態路由功能路由過去
  • 5.根據Spring Cloud Gateway的路由模型定義數據傳輸模型,分別是:路由模型、過濾器模型、斷言模型
//1.創建路由模型
public class GatewayRouteDefinition {
    //路由的Id
    private String id;
    //路由斷言集合配置
    private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
    //路由過濾器集合配置
    private List<GatewayFilterDefinition> filters = new ArrayList<>();
    //路由規則轉發的目標uri
    private String uri;
    //路由執行的順序
    private int order = 0;
    //此處省略get和set方法
}

//2.創建過濾器模型
public class GatewayFilterDefinition {
    //Filter Name
    private String name;
    //對應的路由規則
    private Map<String, String> args = new LinkedHashMap<>();
    //此處省略Get和Set方法
}

//3.路由斷言模型
public class GatewayPredicateDefinition {
    //斷言對應的Name
    private String name;
    //配置的斷言規則
    private Map<String, String> args = new LinkedHashMap<>();
    //此處省略Get和Set方法
}
  • 6.編寫動態路由實現類,需實現ApplicationEventPublisherAware接口
/**
 * 動態路由服務
 */
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware{

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }


    //增加路由
    public String add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
    //更新路由
    public String update(RouteDefinition definition) {
        try {
            delete(definition.getId());
        } catch (Exception e) {
            return "update fail,not find route  routeId: "+definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route  fail";
        }
    }
    //刪除路由
    public Mono<ResponseEntity<Object>> delete(String id) {
        return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
            return Mono.just(ResponseEntity.ok().build());
        })).onErrorResume((t) -> {
            return t instanceof NotFoundException;
        }, (t) -> {
            return Mono.just(ResponseEntity.notFound().build());
        });
    }
}
  • 7.編寫 Rest接口,通過這些接口實現動態路由功能,注意SpringCloudGateway使用的是WebFlux不要引用WebMvc
@RestController
@RequestMapping("/route")
public class RouteController {

    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;

    //增加路由
    @PostMapping("/add")
    public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
        String flag = "fail";
        try {
            RouteDefinition definition = assembleRouteDefinition(gwdefinition);
            flag = this.dynamicRouteService.add(definition);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }
    //刪除路由
    @DeleteMapping("/routes/{id}")
    public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
        try {
            return this.dynamicRouteService.delete(id);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    //更新路由
    @PostMapping("/update")
    public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = assembleRouteDefinition(gwdefinition);
        return this.dynamicRouteService.update(definition);
    }

    //把傳遞進來的參數轉換成路由對象
    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為 lb://consumer-service 時使用下面的方法
            uri = URI.create(gwdefinition.getUri());
        }
        definition.setUri(uri);
        return definition;
    }
}
  • 8.啟動項目,查看網關路由信息,訪問 localhost:9999/actuator/gateway/routes,因沒有配置路由信息,因此返回結果為空數組
    在這里插入圖片描述
  • 9.通過Postman發一個 post 請求新增路由,接口地址:http://localhost:9999/route/update,路由到 consumer-service 上,然后通過網關訪問查看是否轉發請求了(這里直接調用的update,有就會覆蓋,沒有則新增)
    在這里插入圖片描述
  • 10.再訪問 localhost:9999/actuator/gateway/routes ,可以看到新的路由信息已經配置進去了,這就是動態路由配置,還可以調用刪除、修改接口,操作動態操作路由信息
    在這里插入圖片描述
  • 11.配置路由信息后,訪問consumer-service服務,正常返回,說明路由已經生效,請求轉發到consumer-service服務
    在這里插入圖片描述
    好了,動態路由的簡單實現了,一般在生產環境不使用此方式,因為網關都是多實例部署,還可能隨時增加實例,需要已調用接口的方式一一調用網關所有的實例

本篇博客參考了許進的文章,地址:http://springcloud.cn/view/407 ,還有他寫的 《重新定義SpringCloud》比較不錯

代碼已上傳至碼雲,源碼,項目使用的版本信息如下:

- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2 

下篇將介紹動態路由的進階實現,實現方式:

  • 創建一個路由信息維護的項目
  • 實現增刪改查路由信息到mysql
  • 然后發布,發布后將路由信息與版本信息保存到redis中,對外提供 rest 接口
  • 網關開啟定時任務,定時拉取 rest 接口中最新版本的路由信息,這樣網關發布多個實例后,都會單獨的去拉取維護的路由信息
    參考了https://blog.csdn.net/tianyaleixiaowu/article/details/83412301
 
 轉自:https://blog.csdn.net/zhuyu19911016520/article/details/86557165


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM