Gateway实现动态路由

449 阅读3分钟

前几篇已经了解了springboot集成Gateway & Nacos。在前不久的文章里,服务路由的配置时硬编码的实现的,这种配置方式,在启动网关服务后,将无法修改路由配置,若有新服务上线的话,则需要重新部署网关服务。

Gateway路由有两种实现方式

yml配置文件

routes:
  - id: gateway-client
    uri: lb://gateway-client
    predicates:
      - Path=/gclient/**
    filters:
      - AddRequestHeader=X-Request-Token, 2020ABC
      - AddResponseHeader=X-Response-Token, 2020ABC

基于bean加载路由

    @Bean
    public RouteLocator addLocator(RouteLocatorBuilder routeLocatorBuilder){
        return routeLocatorBuilder.routes()
                .route("gateway-client", r -> r.path("/gclient/**")
                        .filters(f->f.addRequestParameter("X-Request-Token","2020ABC"))
                        .uri("lb://gateway-client")).build();
    }

以上两种方式就不符合生产环境的使用。

动态路由

Gateway网关服务在启动的时候,配置的路由信息会加载到内存中,路由信息封装对象为 RouteDefinition ,配置多个RouteDefinition组成网关的路由网络。

@Validated
public class RouteDefinition {

	private String id;

	@NotEmpty
	@Valid
	private List<PredicateDefinition> predicates = new ArrayList<>();

	@Valid
	private List<FilterDefinition> filters = new ArrayList<>();

	@NotNull
	private URI uri;

	private int order = 0;
}

还有一个核心类 RouteDefinitionWriter ,路由配置的处理主要是 RouteDefinitionWriter 类型的bean完成的,为了让配置立即生效,还要用applicationEventPublisher发布进程内消息。

public interface RouteDefinitionWriter {

	Mono<Void> save(Mono<RouteDefinition> route);

	Mono<Void> delete(Mono<String> routeId);

}

简单实现

在生产环境中,我们的路由信息会保存在redis缓存中,或者是mysql等数据库中,下面简单实现gateway动态路由。

工程中整合了gateway、nacos,基础配置基于以前的文章:东小西:springboot集成Gateway & Nacos「再来一刀」

暴露网关服务端点

暴露端点,即可以查看GATEWAY有哪些端点。

① 添加pom依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

② yml添加配置

management:
  endpoints:
    web:
      exposure:
        include: '*'
        exclude: env
  endpoint:
    health:
      show-details: always

③ 通过如下两个url进行访问(端口根据以及ip根据自己设定进行调整)

过滤器模型类

public class MyFilterDefinition {

    private String name;

    private Map<String,String> args = new HashMap<>();


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Map<String, String> getArgs() {
        return args;
    }

    public void setArgs(Map<String, String> args) {
        this.args = args;
    }
}

路由断言模型类

public class MyPredicateDefinition {

    private String name;

    private Map<String,String> args = new HashMap<>();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Map<String, String> getArgs() {
        return args;
    }

    public void setArgs(Map<String, String> args) {
        this.args = args;
    }
}

路由模型类

public class MyRouteDefinition {

    private String id;

    private List<MyFilterDefinition> filters = new ArrayList<>();

    private List<MyPredicateDefinition> predicates = new ArrayList<>();

    private String uri;

    private int order = 0;

    public RouteDefinition getRouteDefinition(){
        RouteDefinition definition = new RouteDefinition();
        definition.setId(this.getId());
        definition.setOrder(this.getOrder());

        //设置断言
        List<PredicateDefinition> pdList = new ArrayList<>();
        List<MyPredicateDefinition> myPredicateDefinitionList = this.getPredicates();
        for (MyPredicateDefinition gpDefinition: myPredicateDefinitionList) {
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setArgs(gpDefinition.getArgs());
            predicate.setName(gpDefinition.getName());
            pdList.add(predicate);
        }

        definition.setPredicates(pdList);

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

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

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public List<MyFilterDefinition> getFilters() {
        return filters;
    }

    public void setFilters(List<MyFilterDefinition> filters) {
        this.filters = filters;
    }

    public List<MyPredicateDefinition> getPredicates() {
        return predicates;
    }

    public void setPredicates(List<MyPredicateDefinition> predicates) {
        this.predicates = predicates;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}

动态路由实现类

实现ApplicationEventPublisherAware接口。

@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    @Resource
    private RouteDefinitionWriter routeDefinitionWriter;


    @Resource
    private ApplicationEventPublisher publisher;

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

    //新增路由
    public void add(MyRouteDefinition myRouteDefinition){
        /**
         * 新增的Actuator Endpoint,刷新路由的时候,先加载路由配置到内存中,
         * 然后再使用RefreshRoutesEvent事件刷新内存中路由配置。
         */
        routeDefinitionWriter.save(Mono.just(myRouteDefinition.getRouteDefinition())).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    // 删除路由
    public void delete(String id){
        routeDefinitionWriter.delete(Mono.just(id)).subscribe();
    }
}

API接口

@RestController
@RequestMapping("/api")
public class RouteSettingController {

    @Resource
    private DynamicRouteServiceImpl dynamicRouteService;

    @PostMapping("/add")
    public String add(@RequestBody MyRouteDefinition myRouteDefinition) {
        try{
            dynamicRouteService.add(myRouteDefinition);
            return "success";
        }catch (Exception e){
            return "error";
        }
    }

    @PostMapping("delete")
    public String delete(String id) {
        try{
            dynamicRouteService.delete(id);
            return "success";
        }catch (Exception e){
            return "error";
        }
    }
}

服务测试

① 启动网关服务工程(springboot-gateway-dynamic-server)和测试服务工程(springboot-gateway-client-a),查看nacos注册情况。

② 查看所有路由,访问地址:http://localhost:9001/actuator/gateway/routes

③ 未配置路由情况下,访问测试服务接口。

④ 配置测试服务路由,再次访问测试服务接口

{
	"id": "springboot-gateway-client-a",
	"uri": "lb://springboot-gateway-client-a",
	"order": 1,
	"predicates": [{
		"name": "Path",
		"args": {
			"pattern": "/sgca/**"
		}
	}]
}

⑤ 调用删除路由接口,再次访问测试服务接口。