spring cloud gateway-动态路由精讲篇

1.为什么需要动态路由

当我们将Gateway的路由配置在yml或者properties文件里时,每当我们部署新的实例,Gateway的配置文件就要更改,重启Gateway服务,这就会导致系统瞬间崩溃,无法在重启Gateway服务时正常使用。一般线上的路由配置我们都是动态配置,让Gateway自己自动获取新的路由配置信息,而不需要重启服务,这样在部署新的服务实例时,Gateway不需重启服务,就能正常访问新部署的服务,其他以前的服务正常运行。下面针对Gateway的路由原理及代码操作实现进行分析。

2.gateway网关启动时,路由信息加载存储在哪里

gateway网关启动时,路由信息默认会加载内存中

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
    private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());


    public InMemoryRouteDefinitionRepository() {
    }


    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap((r) -> {
            if (StringUtils.isEmpty(r.getId())) {
                return Mono.error(new IllegalArgumentException("id may not be empty"));
            } else {
                this.routes.put(r.getId(), r);
                return Mono.empty();
            }
        });
    }


    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap((id) -> {
            if (this.routes.containsKey(id)) {
                this.routes.remove(id);
                return Mono.empty();
            } else {
                return Mono.defer(() -> {
                    return Mono.error(new NotFoundException("RouteDefinition not found: " + routeId));
                });
            }
        });
    }


    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(this.routes.values());
    }
}

3.配置的路由信息怎么进行获取映射的

路由信息被封装到 RouteDefinition 对象中,配置多个RouteDefinition组成gateway的路由系统

1.RouteDefiniton类存储了路由信息

spring cloud gateway-动态路由精讲篇
我们配置的路由信息会被映射到这个实体类里

@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 Map<String, Object> metadata = new HashMap();
    private int order = 0;//优先级 数值越小,优先级越高


    public RouteDefinition() {
    }


    public RouteDefinition(String text) {
        int eqIdx = text.indexOf(61);
        if (eqIdx <= 0) {
            throw new ValidationException("Unable to parse RouteDefinition text '" + text + "', must be of the form name=value");
        } else {
            this.setId(text.substring(0, eqIdx));
            String[] args = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");
            this.setUri(URI.create(args[0]));


            for(int i = 1; i < args.length; ++i) {
                this.predicates.add(new PredicateDefinition(args[i]));
            }


        }
    }


    public String getId() {
        return this.id;
    }


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


    public List<PredicateDefinition> getPredicates() {
        return this.predicates;
    }


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


    public List<FilterDefinition> getFilters() {
        return this.filters;
    }


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


    public URI getUri() {
        return this.uri;
    }


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


    public int getOrder() {
        return this.order;
    }


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


    public Map<String, Object> getMetadata() {
        return this.metadata;
    }


    public void setMetadata(Map<String, Object> metadata) {
        this.metadata = metadata;
    }


    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (o != null && this.getClass() == o.getClass()) {
            RouteDefinition that = (RouteDefinition)o;
            return this.order == that.order && Objects.equals(this.id, that.id) && Objects.equals(this.predicates, that.predicates) && Objects.equals(this.filters, that.filters) && Objects.equals(this.uri, that.uri) && Objects.equals(this.metadata, that.metadata);
        } else {
            return false;
        }
    }


    public int hashCode() {
        return Objects.hash(new Object[]{this.id, this.predicates, this.filters, this.uri, this.metadata, this.order});
    }


    public String toString() {
        return "RouteDefinition{id='" + this.id + ''' + ", predicates=" + this.predicates + ", filters=" + this.filters + ", uri=" + this.uri + ", order=" + this.order + ", metadata=" + this.metadata + '}';
    }
}

最终该实体类RouteDefinition会注入到Spring 容器里

4.Gateway提供的路由操作接口

gateway-core包下面给我们提供了许多路由接口操作
spring cloud gateway-动态路由精讲篇
1.返回了当前网关的路由信息

@GetMapping({"/routes"})
public Flux<Map<String, Object>> routes() {
    return this.routeLocator.getRoutes().map(this::serialize);
}

2.跟据路由Id,查看该路由信息

@GetMapping({"/routes/{id}"})
public Mono<ResponseEntity<Map<String, Object>>> route(@PathVariable String id) {
    return this.routeLocator.getRoutes().filter((route) -> {
        return route.getId().equals(id);
    }).singleOrEmpty().map(this::serialize).map(ResponseEntity::ok).switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
}

3.路由刷新接口,调用触发事件,重新获取redis里的路由信息


@PostMapping({"/refresh"})
public Mono<Void> refresh() {
    this.publisher.publishEvent(new RefreshRoutesEvent(this));
    return Mono.empty();
}

4.获取全局过滤器、过滤器、断言

@GetMapping({"/globalfilters"})
public Mono<HashMap<String, Object>> globalfilters() {
    return this.getNamesToOrders(this.globalFilters);
}


@GetMapping({"/routefilters"})
public Mono<HashMap<String, Object>> routefilers() {
    return this.getNamesToOrders(this.GatewayFilters);
}


@GetMapping({"/routepredicates"})
public Mono<HashMap<String, Object>> routepredicates() {
    return this.getNamesToOrders(this.routePredicates);
}

5.路由新增


@PostMapping({"/routes/{id}"})
public Mono<ResponseEntity<Object>> save(@PathVariable String id, @RequestBody RouteDefinition route) {
    return Mono.just(route).filter(this::validateRouteDefinition).flatMap((routeDefinition) -> {
        return this.routeDefinitionWriter.save(Mono.just(routeDefinition).map((r) -> {
            r.setId(id);
            log.debug("Saving route: " + route);
            return r;
        })).then(Mono.defer(() -> {
            return Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build());
        }));
    }).switchIfEmpty(Mono.defer(() -> {
        return Mono.just(ResponseEntity.badRequest().build());
    }));
}

6.路由删除

@DeleteMapping({"/routes/{id}"})
public Mono<ResponseEntity<Object>> delete(@PathVariable 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());
    });
}

Gateway给我们提供的路由操作接口,怎么访问呢?
1.添加actuator依赖:

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

2.暴露端口
在gateway项目的application.yml中启用gateway api:

#开启actuator管理api,后面要关闭
management:
endpoints:
web:
exposure:
include: "*"

3.访问
http://localhost:网关端口/actuator/接口名称
例如:
访问 http://localhost:网关端口/actuator/gateway/routes,返回了当前网关的路由信息。
spring cloud gateway-动态路由精讲篇

5.自定义类实现路由操作和redis存储

上面介绍了Gateway给我们提供的接口,进行路由的操作,那怎么可以自定义类实现路由操作呢?

1.首先我们先看Gateway内部给我们提供的路由操作的实现

RouteDefinitionWriter接口提供了路由的保存、删除功能,RouteDefinitionLocator 提供了获取所有路由信息功能

public interface RouteDefinitionWriter {    
    //新增
    Mono<Void> save(Mono<RouteDefinition> route);    
    //删除
    Mono<Void> delete(Mono<String> routeId);
}

public interface RouteDefinitionLocator {
    //获取路由所有信息
    Flux<RouteDefinition> getRouteDefinitions();
}

RouteDefinitionRepository接口

public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter {
}

2.自定义类实现RouteDefinitionRepository接口


@Component
public class RedisRouteDefinitionRespository implements RouteDefinitionRepository {
    private final static String GATEWAY_ROUTE_KEY="dynamic_route";


    @Autowired
    RedisTemplate<String,String> redisTemplate;


    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        List<RouteDefinition> routeDefinitionList=new ArrayList<>();
        redisTemplate.opsForHash().values(GATEWAY_ROUTE_KEY).stream().forEach(route->{
            routeDefinitionList.add(JSON.parseObject(route.toString(),RouteDefinition.class));
        });
        return Flux.fromIterable(routeDefinitionList);
    }


    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            redisTemplate.opsForHash().put(GATEWAY_ROUTE_KEY,routeDefinition.getId(), JSON.toJSONString(routeDefinition));
            return Mono.empty();
        });
    }


    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id->{
            if(redisTemplate.opsForHash().hasKey(GATEWAY_ROUTE_KEY,id)){
                redisTemplate.opsForHash().delete(GATEWAY_ROUTE_KEY,id);
                return Mono.empty();
            }
            return Mono.defer(()->Mono.error(new Exception("routeDefinition not found:"+routeId)));
        });
    }
}

6.postman演示路由动态配置和redis存储

1.调用gateway网关向我们暴露的接口

暴露的路由操作接口刚刚已经讲述
actuator/gateway/routes/{id}
路由配置转为json作为参数

{
   "predicates":[
      {
         "name":"Path",
         "args":{
            "_genkey_0":"/test/**"
         }
      },
      {
         "name":"RemoteAddr",
         "args":{
            "_genkey_0":"192.168.56.1/16"
         }
      }
   ],
   "filters":[
      {
         "name":"StripPrefix",
         "args":{
            "_genkey_0":"1"
         }
      }
   ],
   "uri":"lb://test",
   "order":0
}

2.redis存储提交的路由配置信息

postman提交请求后,查看redis

spring cloud gateway-动态路由精讲篇

3.刷新(refresh)

通过动态路由,我们以后发布时,不需重启网关服务了,只需通过暴露的接口进行路由操作,最后通过actuator/gateway/refresh接口,实现网关配置的刷新(会重新从redis拉取配置)

源码分析:

提供的刷新接口

@PostMapping({"/refresh"})
public Mono<Void> refresh() {    
    this.publisher.publishEvent(new RefreshRoutesEvent(this));            return Mono.empty();
}

监听事件,一旦调用refresh接口,便会重新从redis获取路由配置信息

public void onApplicationEvent(RefreshRoutesEvent event) {
    this.fetch().materialize().collect(Collectors.toList()).doOnNext((routes) -> {
        //重新获取路由配置信息
        List var10000 = (List)this.cache.put("routes", routes);
    }).subscribe();
}
匿名

发表评论

匿名网友