前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习一下SpringCloudGateWay

学习一下SpringCloudGateWay

原创
作者头像
eeaters
修改2022-02-21 18:04:40
4330
修改2022-02-21 18:04:40
举报
文章被收录于专栏:阿杰阿杰
  • 文档
  • 目标
  • 前置技能
    • spring-webFlux
    • SpringBootActuator
  • GateWay的词汇
    • Route
    • Predicate
    • Filter
  • 配置化使用和手动实现
    • 配置化
    • 手动实现
  • 再谈Route
    • DispatcherHandler
    • WebHandler
    • RoutePredicateHandlerMapping
    • GlobalFilter
    • GatewayFilterFactory
    • RoutePredicateFactory
    • Route
    • RouteLocator
  • 动态路由调整
    • CustomDynamic
    • 测试

文档

目标

了解一下网关的机制; 了解一下怎么样实现一个动态路由的网关

前置技能

spring-webFlux

SpringCloudGateway 的文档上明确写了, 项目是基于Spring Boot 2.x, Spring WebFlux进行构建. SpringBoot项目在项目启动的时候会主动探测 deduceFromClasspath 当前容器的环境并进行构建,WebFlux和SpringMvc的功能和结构很类似, 但是由于响应式编程和传统的同步阻塞式的编程差异较大, 因此WebFlux重复造了一些轮子, 如:

  • DispatcherHandler(flux) = DispatcherServlet(mvc)
  • HandlerMapping(flux) = HandlerMapping(mvc)
  • HandlerResultHandler(flux) = HandlerResultHandler(mvc)

在gateway中常用的类如下:

  • ServerWebExchange : 请求和响应的契约,有点类似于一次请求的上下文
  • ServerHttpRequest : 请求
  • ServerHttpResponse : 响应

SpringBootActuator

只要使用了SpringBoot项目,对这个服务监控的依赖应该或多或少有一定的接触, 在SpringCloud文档的第15章中有介绍, 但是我是先浏览了一遍官方文档的, 所以将这个提前,便于学习的时候能够了解网关内部的一些数据, 作为demo,直接将所有监控数据通过web接口暴露应该不过分吧,引入依赖加配置:

代码语言:javascript
复制
management:
  endpoints:
    web:
      exposure:
        include: '*'

加入配置之后,可以在 GatewayControllerEndpoint 这个类查看多暴露出的诸多web接口;

GateWay的词汇

Route

路由,gateway的基本构成块,内部持有了该路由的断言集合和过滤器集合, 其中断言必须全部命中才允许通过,通过后会执行所有的过滤器以及目的URI

Predicate

断言,入参是ServerWebExchange,也就是说我们可以取出Request和Response进行判断 比如:请求的时间,请求的header,请求的参数,请求路径 等;

Filter

过滤器,入参是ServerWebExchange和FilterChain, 我们可以在这个链中对请求的入参和响应进行调整 比如加Header,加Cookie,直接修改URI,基于注册中心时基于一定规则路由到不同的服务中 等;

配置化使用和手动实现

配置化

下面的配置可以将所有请求时间在2022-01-25

代码语言:javascript
复制
spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://example.org
          predicates:
            - After=2022-01-25T07:42:47.789-07:00[America/Denver]

手动实现

手动造一个和上面效果一样的轮子,从 PredicateSpec 类中参考原生的实现写一个

断言
代码语言:javascript
复制
public class BeforeRouteDemoPredicateFactory extends AbstractRoutePredicateFactory<BeforeRouteDemoPredicateFactory.Config> {
​
    private final static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
​
    public static final String CONFIG_KEY = "before";
​
    public BeforeRouteDemoPredicateFactory() {
        super(Config.class);
    }
​
    public Predicate<ServerWebExchange> apply(Config config) {
        return serverWebExchange -> {
            LocalDate now = LocalDate.now();
            return now.isAfter(config.getLocalDate());
        };
    }
​
    public static class Config {
        private LocalDate localDate;
​
        public LocalDate getLocalDate() {
            return localDate;
        }
​
        public void setLocalDate(String localDate) {
            this.localDate = LocalDate.parse(localDate, DATE_TIME_FORMATTER);
        }
    }
}
过滤器
代码语言:javascript
复制
/**
* 在返回的response上加一个cookie
*/
public class AddResponseCookiesDemoFilterFactory extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
​
    @Override
    public GatewayFilter apply(Consumer<NameConfig> consumer) {
        return apply(new NameConfig());
    }
​
    @Override
    public GatewayFilter apply(NameConfig config) {
        return (exchange, chain) -> chain.filter(exchange)
                .then(Mono.fromRunnable(()->{
                    ServerHttpResponse response = exchange.getResponse();
                    ResponseCookie cookie = ResponseCookie.from("custom.demo", "gateway-demo").build();
                    response.addCookie(cookie);
                }));
    }
}
路由

路由是通过构造者模式产生,直接自动装配中进行装配即可

自动装配
代码语言:javascript
复制
@Configuration
@EnableConfigurationProperties(DemoAutoConfigProperties.class)
public class RouteExampleConfiguration {
​
    @Bean
    public BeforeRouteDemoPredicateFactory beforeRouteDemoPredicateFactory() {
        return new BeforeRouteDemoPredicateFactory();
    }
​
    @Bean
    public AddResponseCookiesDemoFilterFactory addResponseCookiesDemoFilterFactory() {
        return new AddResponseCookiesDemoFilterFactory();
    }
​
    @Bean
    public RouteLocator testRouteLocator(RouteLocatorBuilder locatorBuilder, DemoAutoConfigProperties properties) {
        BeforeRouteDemoPredicateFactory predicateFactory = beforeRouteDemoPredicateFactory();
        AddResponseCookiesDemoFilterFactory filterFactory = addResponseCookiesDemoFilterFactory();
​
        Predicate<ServerWebExchange> exchangePredicate = predicateFactory.apply(config -> config.setLocalDate(properties.getBefore()));
        GatewayFilter gatewayFilter = filterFactory.apply(config -> {
        });
        return locatorBuilder.routes()
                .route("demo.route",
                        r -> r.predicate(exchangePredicate)
                                .filters(f -> f.filter(gatewayFilter))
                                .uri(properties.getUri())
                ).build();
    }
}
配置文件

东西要全套进行熟悉,简单写了两个需要配置的变量

代码语言:javascript
复制
demo:
  gateway:
    before: 2022-01-24
    uri: https://example.com

再谈Route

手写例子之后,感觉gateway也就是三板斧的事情: route, predicate, filter

因此简单梳理下gateway的请求流程:

DispatcherHandler

WebFlux的中央控制器,和SpringMvc的DispatcherServlet一样,协调所有请求; GateWay是基于WebFlux的,因此入口就是这里

WebHandler

WebFlux定义了WebHandler以开放扩展,GateWay通过实现该类来实现内部的流程; 可以说是GateWay内部小天地的入口; 具体实现类: FilteringWebHandler

RoutePredicateHandlerMapping

HandlerMapping的功能和SpringMvc一样; 拓展了一点功能 内部持有了 RouteLocator , 也就是网关所有的路由功能都可以获取到; 当进行请求映射的时候,通过RouteLocator类找到适合的Route ; 然后丢到ServerWebExchange中; 就是将Route的信息丢到了全局上下文中便于使用

GlobalFilter

类的注释上有描述:

代码语言:javascript
复制
对Web请求进行拦截式、链式处理的契约,
这些请求可用于实现横切的、与应用程序无关的需求,如安全性、超时等。

我们的route都需要配置uri ; 并且必须有schema,比如:http/https/lb/forward ; 他们的实现原理就是通过GlobalFilter来实现的;

不同于Filter和Predicate,uri的模式是统一的,是直接设置为全局的Filter, 所有请求都执行一下,看看应该走哪一种规则就可以了,因此不需要工厂 如: ForwardPathFilter / ReactiveLoadBalancerClientFilter / RouteToRequestUrlFilter , 不过uri应该在最后执行,所以也实现了Ordered接口,保证执行的优先级低

GatewayFilterFactory

用来生成GateWay内部的拦截器的工厂类,阅读 GatewayFilterSpec 类可以多进行了解

RoutePredicateFactory

用来生成断言的工厂类, 阅读 PredicateSpec 类可以多进行了解

Route

组合Filter、Predicate和uri ; 这个Route在HandlerMapping阶段就已经放到了上下文中; 便于后续流程中使用;又重要又简单的类

RouteLocator

RoutePredicateHandlerMapping中有提到这个接口; 这个接口就是route的定位器; 持有了所有的Route规则, 通常我们要新增一个Route也是在RouteLocatorBuilder 的基础上,进行增加; 这个接口对外暴露只有一个功能; 就是返回所有的路由

动态路由调整

按照官方网站测了好多demo,但是都是启动的时候直接将所有的路由进行加载的, 实际使用过程中肯定是需要给网关加配置的;

  • 采用远程配置中心的方式,对配置变更做好监听
  • 采用一个DB,或者Redis,网关定时轮询

前面actuator的时候提到了 GatewayControllerEndpoint, 通过这个web端点,GateWay已经贴心的支持了这些事情 ;

基本流程只有两个步骤:

  • RouteDefinitionWriter 修改路由规则的定义
  • 发布路由变更的事件,要求重新加载路由规则

CustomDynamic

我们直接注入RouteDefinitionRepository进行调整也行

代码语言:javascript
复制
@Component
public class CustomDynamic {
​
    //默认 InMemoryRouteDefinitionRepository
    @Autowired
    RouteDefinitionRepository routeDefinitionRepository;
​
    // 这样子引入的是: CompositeRouteDefinitionLocator; 组合了多个定位器
    @Autowired
    RouteDefinitionLocator routeDefinitionLocator;
​
    public void save(RouteDefinition routeDefinition) {
        Mono<RouteDefinition> definitionMono = Mono.just(routeDefinition);
        routeDefinitionRepository.save(definitionMono).block();
    }
​
    public Flux<RouteDefinition> list() {
        return routeDefinitionLocator.getRouteDefinitions();
    }
​
    public void delete(String routeId) {
        routeDefinitionRepository.delete(Mono.just(routeId)).block();
    }
​
}
DynamicRunner
代码语言:javascript
复制
@Component
public class DynamicRunner implements CommandLineRunner {
    @Autowired
    CustomDynamic customDynamic;
    @Autowired
    ApplicationEventPublisher publisher;
    @Override
    public void run(String... args) throws Exception {
        Flux<RouteDefinition> list = customDynamic.list();
        list.subscribe(System.out::println);

        customDynamic.save(buildSimpleRouteDefinition());

        TimeUnit.SECONDS.sleep(30);
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    public RouteDefinition buildSimpleRouteDefinition() {
        RouteDefinition routeDefinition = new RouteDefinition();
        routeDefinition.setId("dynamic.demo");
        routeDefinition.setOrder(0);
        routeDefinition.setUri(URI.create("https://example.com"));

        PredicateDefinition predicateDefinition = new PredicateDefinition();
        predicateDefinition.setName("After");
        Map<String, String> map = new HashMap<>();
        map.put("datetime", "2012-01-24T17:42:47.789-07:00[America/Denver]");
        predicateDefinition.setArgs(map);
        List<PredicateDefinition> definitionArrayList = new ArrayList<>();
        definitionArrayList.add(predicateDefinition);

        routeDefinition.setPredicates(definitionArrayList);
        return routeDefinition;
    }
}

测试

原以为很复杂,但是仿照了 AbstractGatewayControllerEndpoint 后, 这个功能反而显得很简单,只要监听远成配置或者定时任务代替runner就是动态的路由调整了

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文档
  • 目标
  • 前置技能
    • spring-webFlux
      • SpringBootActuator
      • GateWay的词汇
        • Route
          • Predicate
            • Filter
            • 配置化使用和手动实现
              • 配置化
                • 手动实现
                  • 断言
                  • 过滤器
                  • 路由
                  • 自动装配
                  • 配置文件
              • 再谈Route
                • DispatcherHandler
                  • WebHandler
                    • RoutePredicateHandlerMapping
                      • GlobalFilter
                        • GatewayFilterFactory
                          • RoutePredicateFactory
                            • Route
                              • RouteLocator
                              • 动态路由调整
                                • CustomDynamic
                                  • DynamicRunner
                                • 测试
                                相关产品与服务
                                云数据库 Redis
                                腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档