欢迎访问Spring Cloud中国社区

我们致力于成为中国最专业的Spring Boot和Spring Cloud交流社区。推荐使用Github直接登录,欢迎加微信号Software_King进入社区微信交流群。若发现网站bug欢迎反馈!

Spring Cloud Gateway动态路由配置

admin · 10天前 · 374 ·

Spring Cloud Gateway官方教程讲的都是提前在配置文件中配置网关,实际项目中,Spring Cloud Gateway作为微服务的入口,需要尽量避免重启,所以我们需要在Spring Cloud Gateway运行时动态配置网关。

1. Spring Cloud Gateway自带接口

Spring Cloud Gateway在2018年6月份发布了2.0第一个release版本,官方文档并没有讲如何动态配置,翻看Spring Cloud Gateway源码,发现类org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint中提供了网关配置Restful接口,默认没有启用。在类org.springframework.cloud.gateway.config.GatewayAutoConfiguration中配置了GatewayControllerEndpoint

  1. @Configuration
  2. @ConditionalOnClass(Health.class)
  3. protected static class GatewayActuatorConfiguration {
  4. @Bean
  5. @ConditionalOnEnabledEndpoint
  6. public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
  7. List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,
  8. RouteLocator routeLocator) {
  9. return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator);
  10. }
  11. }

存在org.springframework.boot.actuate.health.Health时启用,添加actuator依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

在gateway项目的application.yml中启用gateway api:

  1. #开启actuator管理api,后面要关闭
  2. management:
  3. endpoints:
  4. web:
  5. exposure:
  6. include: "*"

访问 http://localhost:网关端口/actuator/gateway/routes,返回了当前网关的路由信息。

2. 编码方式

不过这里我们不打算用自带的Restful接口,一来官方文档也没说新增网关参数怎么传,再者我们也不希望在网关暴露这些接口。参照org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint,我们自己编程来动态改变网关。

直接上代码:

  1. import java.net.URI;
  2. import java.util.Arrays;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
  7. import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
  8. import org.springframework.cloud.gateway.route.RouteDefinition;
  9. import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
  10. import org.springframework.context.ApplicationEventPublisher;
  11. import org.springframework.context.ApplicationEventPublisherAware;
  12. import org.springframework.stereotype.Service;
  13. import org.springframework.web.util.UriComponentsBuilder;
  14. import reactor.core.publisher.Mono;
  15. @Service
  16. public class TestGatewayService implements ApplicationEventPublisherAware {
  17. @Autowired
  18. private RouteDefinitionWriter routeDefinitionWriter;
  19. private ApplicationEventPublisher publisher;
  20. public String save() {
  21. RouteDefinition definition = new RouteDefinition();
  22. PredicateDefinition predicate = new PredicateDefinition();
  23. Map<String, String> predicateParams = new HashMap<>(8);
  24. definition.setId("baiduRoute");
  25. predicate.setName("Path");
  26. predicateParams.put("pattern", "/baidu");
  27. predicateParams.put("pathPattern", "/baidu");
  28. predicate.setArgs(predicateParams);
  29. definition.setPredicates(Arrays.asList(predicate));
  30. URI uri = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri();
  31. definition.setUri(uri);
  32. routeDefinitionWriter.save(Mono.just(definition)).subscribe();
  33. this.publisher.publishEvent(new RefreshRoutesEvent(this));
  34. return "success";
  35. }
  36. @Override
  37. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
  38. this.publisher = applicationEventPublisher;
  39. }
  40. }

代码说明:

1、predicate.setName(“Path”); 设置predicat名称,这个名称不是乱起的,Spring会根据名称去查找对应的FilterFactory,目前支持的名称有:After、Before、Between、Cookie、Header、Host、Method、Path、Query、RemoteAddr。

对应官方文档的Route Predicate Factories(http://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html)

2、definition.setId(“baiduRoute”); 设置definition的id,需要全局唯一,默认使用UUID。

3、predicateParams.put(“pattern”, “/baidu”); 每个Route Predicate的参数不同,详情在官网文档查看对应的Route Predicate配置示例,然而官方文档也很坑,比如Path Route的- Path=/foo/{segment},把参数给省略了。还是得看源码,在包org.springframework.cloud.gateway.handler.predicate里有Spring Cloud Gateway所有的Predicate,打开对应的RoutePredicateFactory,内部类Config就是该Predicate支持的参数。

4、routeDefinitionWriter.save(Mono.just(definition)).subscribe(); 默认的RouteDefinitionWriter实现类是org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository。注意最后一定要调用subscribe(),否则不执行。

至此已编码动态配置了一个基本的网关。

3. 带Filter的配置代码

  1. RouteDefinition routeDefinition = new RouteDefinition();
  2. PredicateDefinition predicateDefinition = new PredicateDefinition();
  3. Map<String, String> predicateParams = new HashMap<>(8);
  4. Map<String, String> filterParams = new HashMap<>(8);
  5. FilterDefinition filterDefinition = new FilterDefinition();
  6. URI uri = UriComponentsBuilder.fromUriString("lb://HELLO-SERVICE").build().toUri();
  7. routeDefinition.setId("rateLimitTest");
  8. // 名称是固定的,spring gateway会根据名称找对应的PredicateFactory
  9. predicateDefinition.setName("Path");
  10. predicateParams.put("pattern", "/rate/**");
  11. predicateDefinition.setArgs(predicateParams);
  12. // 名称是固定的,spring gateway会根据名称找对应的FilterFactory
  13. filterDefinition.setName("RequestRateLimiter");
  14. // 每秒最大访问次数
  15. filterParams.put("redis-rate-limiter.replenishRate", "2");
  16. // 令牌桶最大容量
  17. filterParams.put("redis-rate-limiter.burstCapacity", "3");
  18. // 限流策略(#{@BeanName})
  19. filterParams.put("key-resolver", "#{@hostAddressKeyResolver}");
  20. // 自定义限流器(#{@BeanName})
  21. //filterParams.put("rate-limiter", "#{@redisRateLimiter}");
  22. filterDefinition.setArgs(filterParams);
  23. routeDefinition.setPredicates(Arrays.asList(predicateDefinition));
  24. routeDefinition.setFilters(Arrays.asList(filterDefinition));
  25. routeDefinition.setUri(uri);
  26. routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
  27. publisher.publishEvent(new RefreshRoutesEvent(this));

需要注意的是filterParams.put(“key-resolver”, “#{@hostAddressKeyResolver}”); hostAddressKeyResolver是我自定义的Spring Bean,#{@BeanName}是Spring的表达式,用来注入Bean。

4. 自定义RouteDefinitionWriter

Spring Cloud Gateway默认的RouteDefinitionWriter实现类是org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository,Route信息保存在当前实例的内存中,这在集群环境中会存在同步问题。我们可以自定义一个基于Redis的RouteDefinitionWriter。

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.cloud.gateway.route.RouteDefinition;
  5. import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
  6. import org.springframework.cloud.gateway.support.NotFoundException;
  7. import org.springframework.data.redis.core.StringRedisTemplate;
  8. import org.springframework.stereotype.Component;
  9. import com.alibaba.fastjson.JSON;
  10. import reactor.core.publisher.Flux;
  11. import reactor.core.publisher.Mono;
  12. /**
  13. * 使用Redis保存自定义路由配置(代替默认的InMemoryRouteDefinitionRepository)
  14. * <p/>
  15. * 存在问题:每次请求都会调用getRouteDefinitions,当网关较多时,会影响请求速度,考虑放到本地Map中,使用消息通知Map更新。
  16. *
  17. * @since 2018年7月9日 下午2:39:02
  18. */
  19. @Component
  20. public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
  21. public static final String GATEWAY_ROUTES = "geteway_routes";
  22. @Autowired
  23. private StringRedisTemplate redisTemplate;
  24. @Override
  25. public Flux<RouteDefinition> getRouteDefinitions() {
  26. List<RouteDefinition> routeDefinitions = new ArrayList<>();
  27. redisTemplate.opsForHash().values(GATEWAY_ROUTES).stream().forEach(routeDefinition -> {
  28. routeDefinitions.add(JSON.parseObject(routeDefinition.toString(), RouteDefinition.class));
  29. });
  30. return Flux.fromIterable(routeDefinitions);
  31. }
  32. @Override
  33. public Mono<Void> save(Mono<RouteDefinition> route) {
  34. return route
  35. .flatMap(routeDefinition -> {
  36. redisTemplate.opsForHash().put(GATEWAY_ROUTES, routeDefinition.getId(),
  37. JSON.toJSONString(routeDefinition));
  38. return Mono.empty();
  39. });
  40. }
  41. @Override
  42. public Mono<Void> delete(Mono<String> routeId) {
  43. return routeId.flatMap(id -> {
  44. if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTES, id)) {
  45. redisTemplate.opsForHash().delete(GATEWAY_ROUTES, id);
  46. return Mono.empty();
  47. }
  48. return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
  49. });
  50. }
  51. }

原文链接:https://my.oschina.net/tongyufu/blog/1844573