文档章节

Spring Cloud Gateway运行时动态配置网关

夜雨寄北09
 夜雨寄北09
发布于 07/11 18:40
字数 1145
阅读 6310
收藏 23

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

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

@Configuration
@ConditionalOnClass(Health.class)
protected static class GatewayActuatorConfiguration {

    @Bean
    @ConditionalOnEnabledEndpoint
    public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
                                                            List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter,
                                                            RouteLocator routeLocator) {
        return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator);
    }
}

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

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

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

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

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

编码方式

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

直接上代码:

import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import reactor.core.publisher.Mono;

@Service
public class TestGatewayService implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter     routeDefinitionWriter;
    private ApplicationEventPublisher publisher;

    public String save() {
        RouteDefinition definition = new RouteDefinition();
        PredicateDefinition predicate = new PredicateDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);

        definition.setId("baiduRoute");
        predicate.setName("Path");
        predicateParams.put("pattern", "/baidu");
        predicateParams.put("pathPattern", "/baidu");
        predicate.setArgs(predicateParams);
        definition.setPredicates(Arrays.asList(predicate));
        URI uri = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri();
        definition.setUri(uri);
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }

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

代码说明:

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(),否则不执行。

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

带Filter的配置代码:

RouteDefinition routeDefinition = new RouteDefinition();
PredicateDefinition predicateDefinition = new PredicateDefinition();
Map<String, String> predicateParams = new HashMap<>(8);
Map<String, String> filterParams = new HashMap<>(8);
FilterDefinition filterDefinition = new FilterDefinition();
URI uri = UriComponentsBuilder.fromUriString("lb://HELLO-SERVICE").build().toUri();

routeDefinition.setId("rateLimitTest");
// 名称是固定的,spring gateway会根据名称找对应的PredicateFactory
predicateDefinition.setName("Path");
predicateParams.put("pattern", "/rate/**");
predicateDefinition.setArgs(predicateParams);

// 名称是固定的,spring gateway会根据名称找对应的FilterFactory
filterDefinition.setName("RequestRateLimiter");
// 每秒最大访问次数
filterParams.put("redis-rate-limiter.replenishRate", "2");
// 令牌桶最大容量
filterParams.put("redis-rate-limiter.burstCapacity", "3");
// 限流策略(#{@BeanName})
filterParams.put("key-resolver", "#{@hostAddressKeyResolver}");
// 自定义限流器(#{@BeanName})
//filterParams.put("rate-limiter", "#{@redisRateLimiter}");
filterDefinition.setArgs(filterParams);

routeDefinition.setPredicates(Arrays.asList(predicateDefinition));
routeDefinition.setFilters(Arrays.asList(filterDefinition));
routeDefinition.setUri(uri);
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));

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

自定义RouteDefinitionWriter

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

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 使用Redis保存自定义路由配置(代替默认的InMemoryRouteDefinitionRepository)
 * <p/>
 * 存在问题:每次请求都会调用getRouteDefinitions,当网关较多时,会影响请求速度,考虑放到本地Map中,使用消息通知Map更新。
 *
 * @since 2018年7月9日 下午2:39:02
 */
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    public static final String  GATEWAY_ROUTES = "geteway_routes";
    @Autowired
    private StringRedisTemplate redisTemplate;

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

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route
                .flatMap(routeDefinition -> {
                    redisTemplate.opsForHash().put(GATEWAY_ROUTES, 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_ROUTES, id)) {
                redisTemplate.opsForHash().delete(GATEWAY_ROUTES, id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
        });
    }

}

 

© 著作权归作者所有

共有 人打赏支持
夜雨寄北09
粉丝 10
博文 16
码字总数 11686
作品 0
浦东
程序员
私信 提问
加载中

评论(5)

一条奋进的于

引用来自“一条奋进的于”的评论

直接报Failed to handle request [GET http://localhost:8082/baidu]: Response status 404这个错误

引用来自“Veadan”的评论

你解决了吗?/actuator/gateway/routes可以拿到redis中的配置,但是配置文件中注释掉就404了
谢谢大神,已经解决了:relaxed:
Veadan
Veadan

引用来自“一条奋进的于”的评论

直接报Failed to handle request [GET http://localhost:8082/baidu]: Response status 404这个错误
你解决了吗?/actuator/gateway/routes可以拿到redis中的配置,但是配置文件中注释掉就404了
付智_无限挑战
付智_无限挑战
给你点赞。
youngjdong
youngjdong
不错,谢谢
一条奋进的于
直接报Failed to handle request [GET http://localhost:8082/baidu]: Response status 404这个错误
Spring Cloud Finchley 正式发布,兼容 Spring Boot 2.0.x

经过数个前期测试版本,Spring Cloud Finchley 终于迎来了正式版本: 下载地址 发行说明 显著变化: 与 Spring Boot 2.0.x 兼容 不支持 Spring Boot 1.5.x 最低要求 Java 8 新增 Spring Clo...

淡漠悠然
06/20
0
12
微服务网关Zuul迁移到Spring Cloud Gateway

背景 在之前的文章中,我们介绍过微服务网关Spring Cloud Netflix Zuul,前段时间有两篇文章专门介绍了Spring Cloud的全新项目Spring Cloud Gateway,以及其中的过滤器工厂。本文将会介绍将微...

aoho
09/24
0
0
Spring Cloud Gateway中的权重路由

摘要:本文主要通过运用Spring Cloud Gateway的WeightRoutePredicateFactory对URL进行权重路由。 1.权重路由 1.1 权重路由使用场景 在开发或者测试的时候,或者线上发布,线上服务多版本控制的...

SpringCloud社区
06/27
0
0
Spring Cloud Gateway 整合Eureka路由转发

前面我们对Spring Cloud Gateway进行了一个入门的学习,具体文章可以查看《Spring Cloud Gateway 网关尝鲜》进行学习。 网关负责转发工作,那么它需要知道后端的服务信息,今天我们来学习下S...

尹吉欢
07/13
0
0
微服务下使用网关 Spring Cloud Gateway

Spring Cloud Gateway 工作原理 客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特定的请求过滤器链。 过滤器之...

Anoyi
06/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Fragment 退出动画导致fragment退出失败问题(罕见问题)

问题背景: Fragment: A , B; A is hidden; B is added; 操作: 返回键的时候执行操作: B transaction remove ; A transaction attach; 执行移除动画。transaction.setCustomAnimations(i......

Carlyle_Lee
25分钟前
1
0
Java并发编程学习四:CountDownLatch,CyclicBarrier,Semaphore以及原子类

上篇文章线程同步的关键字以及理解中介绍了一下多线程同步协作之间经常使用的关键字,今天这篇文章就介绍一下一些同步类以及原子类的使用吧。Java中提供了不少的同步类,如:CountDownLatch,...

JerryLin123
32分钟前
1
0
面试专题-框架

Spring Spring有哪些特点? 使用Spring有什么好处? 1 应用解耦 2 依赖注入 3 AOP 4 事务管理 5 MVC 6 集成开发 Spring应用程序看起来像什么? 一些接口及其实现 一些POJO类 一些xml配置文件 ...

这很耳东先生
43分钟前
2
0
锁和分布式锁

锁的由来: 多线程环境中,经常遇到多个线程访问同一个 共享资源 ,这时候作为开发者必须考虑如何维护数据一致性,这就需要某种机制来保证只有满足某个条件(获取锁成功)的线程才能访问资源...

Ala6
43分钟前
3
0
Vue + Vue-CLI + Mint-UI 移动端开发新手示例实战 - by dogstar

Vue + Vue-CLI + Mint-UI 移动端开发新手示例实战 - by dogstar 项目源代码,在码云上:https://gitee.com/dogstar/a-vue-mint-demo 快速使用 # 安装依赖npm install# 本地开发调试 lo...

暗夜在火星
57分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部