springCloud Finchley.RELEASE gateway 动态网关实现及gateway功能综述,优化

SpringCloud gateway已经出来很长时间,现在正好要做一个动态网关,落个笔录备忘。现在微博上很多网关在项目上的使用性比较差,本文会给出一个完整的方案及中间版本。

目录

网关核心功能

SpringCloud gateway

Gateway包含哪些功能呢

Gateway的工作方式

Predicate

Filter

如何动态加载路由

首先看如何加载的

加载顺序


网关核心功能

  1. 路由转发
  2. 限流熔断
  3. 黑白名单
  4. 统一安全认证
  5. 统一日志监控
  6. 单点入口

在这里,只对springcloud gateway做梳理讲解。

Springcloud gateway是基于Rective Streams 的 WebFlux框架。其主要优势、原理等不在本文阐述。

SpringCloud gateway

Gateway包含哪些功能呢

  1. 动态路由:能够匹配任何请求的属性
  2. 可以对路由指定Predicate(断言)与Filter,gateway已经实现了11种Predicate方式,数种Filter,并且可自定义配置,指定运行的predicate与filter,gateway会自动解释执行。
  3. 集成断路器
  4. 请求限流功能
  5. 支持路径重写

 

Gateway的工作方式

  1. 看下图,来源于官方

      springCloud Finchley.RELEASE gateway 动态网关实现及gateway功能综述,优化_第1张图片

 

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由(路由转发,即请求和路由进行匹配,predicate),将其发送到 Gateway Web Handler。Handler 再通过指 定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

 

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

2. 先看如下配置:

springCloud Finchley.RELEASE gateway 动态网关实现及gateway功能综述,优化_第2张图片

 

请求通过predicates映射,符合此时间规则,路由到lb://enn-dfs:6002服务,并且在请求服务之前,执行AddRequestParameter过滤器。

Predicate

重点讲下predicate,我现在基于springCloud Finchley.RELEASE版本开发。
在此版本中,gateway实现了11种不同的predicate路由。
RoutePredicateFactory 接口继承自 ShortcutConfigurable 接口,ShortcutConfigurable 接口在多个实现类中都有出现,根据传入的具体 RouteDefinitionLocator 获取路由定义对象时,就用到了该接口中的默认方法。路由谓词的种类很多,不同的谓词需要的配置参数不一样,所以每种 路由谓词(断言)和过滤器的实现都会实现 ShortcutConfigurable 接口,来指定自身参数的个数和顺序。下述predicate的实现继承了抽象类AbstractRoutePredicateFactory,而此类实现了RoutePredicateFactory。在此不粘贴源码,自己去看。
1. 请求时间校验条件谓语,分别是请求时间满足在配置时间之后、之前、之间。命中后映射请求

"After" -> [AfterRoutePredicateFactory@ configClass = AfterRoutePredicateFactory.Config]
"Before" -> [BeforeRoutePredicateFactory@ configClass = BeforeRoutePredicateFactory.Config]
"Between" -> [BetweenRoutePredicateFactory@ configClass = BetweenRoutePredicateFactory.Config]
2. 请求Cookie校验条件谓语,cookie正则匹配

"Cookie" -> [CookieRoutePredicateFactory@ configClass = CookieRoutePredicateFactory.Config]
3. 请求header校验条件谓语,正则匹配

"Header" -> [HeaderRoutePredicateFactory@ configClass = HeaderRoutePredicateFactory.Config]
4. 请求Host,即根据host匹配,命中映射请求roteUrl

"Host" -> [HostRoutePredicateFactory@ configClass = HostRoutePredicateFactory.Config]
5. 根据方法名称

"Method" -> [MethodRoutePredicateFactory@ configClass = MethodRoutePredicateFactory.Config]
6. 根据路径,此方式应该是最常用的方式,通过requestMapping路径映射。

"Path" -> [PathRoutePredicateFactory@ configClass = PathRoutePredicateFactory.Config]
7. 通过查询参数

"Query" -> [QueryRoutePredicateFactory@ configClass = QueryRoutePredicateFactory.Config]
"ReadBodyPredicateFactory" -> [ReadBodyPredicateFactory@ configClass = ReadBodyPredicateFactory.Config]
8. 通过地址

"RemoteAddr" -> [RemoteAddrRoutePredicateFactory@ configClass = RemoteAddrRoutePredicateFactory.Config]
9. 基于路由权重

"Weight" -> [WeightRoutePredicateFactory@ configClass = WeightConfig]
基于权重的稍微难理解些,后边做解释。
其他只对path进行解释,因为现今只使用了path

基于Path断言详述

根据路径断言相对来说比较好理解,看个例子就明白了,后续的动态路由中配置数据,也是基于Path,可以具体看下面。

spring:

  cloud:

    gateway:

      routes:

      - id: host_route

        uri: http://example.org

        predicates:

        - Path=/foo/{segment}

 

基于路由权重断言详述

WeightRoutePredicateFactory 实现了路由权重的功能,按照路由权重选择 同一个分组中的路由。

spring:
  cloud:
    gateway:
      locator:
        enabled: true
      routes:
        -id: weight_route1
          uri: lb://enn-dfs:6002
          order: 6000
          predicates:
          -Weight= group3, 1
          -Path=/weight/**
          filters:
          -StripPrefix= 2
        -id: weight_route2
          uri: lb://enn-dfs:6003
          order: 6000
          predicates:
          -Path=/weight/**
          -Weight= group3, 9
          filters:
          -StripPrefix=1

weight_ route1 权重为 1, weight_ route2 权重为9。 对于10个访问/ weight/** 路径的请求来说,将会有9个路由到 weight_ route2,1个路由到 weight_ route1。

 

WeightRoutePredicateFactory 权重算法实现过程

参考:https://www.cnblogs.com/liukaifeng/p/10055866.html

weight_ route1: group3, 1weight_ route2: group3, 9

实现过程为:

1)构造 weightsgroup3)数组:weights=[ 1 9]

2normalize weights= weights/ sum weights=[ 0. 1 0. 9]
3
)计算区间范围: ranges= weights. collect 0,( s w-> s+ w=[ 0 0. 1 1. 0]

4)生成 随机数: r= random()

5)搜索随机数所在的区间: i= integer s. t. r>= ranges[ i]&& r< ranges[ i+ 1]

6)选择相应的路由: routes[ i]

网关应用服务在启动时会发布 WeightDefinedEvent,在 WeightCalculatorWebFilter 过滤器中定义了该时间监听器,当接收到时间 WeightDefinedEvent 时,会自动添加 WeightConfig 到权重配置中。请求在经过 WeightCalculatorWebFilter 时会 生成 一个 随机数, 根据随机数所在的区间选择对应分组的路由。

// WeightRoutePredicateFactory.java

@Override
public Predicate apply(Config config) {
   	List sources = convert(config.sources);               
	return exchange -> {
            InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange);
			if(remoteAddress != null) {
                String hostAddress = remoteAddress.getAddress().getHostAddress();
                String host = exchange.getRequest().getURI().getHost();                                  
				if(log.isDebugEnabled() && !hostAddress.equals(host)) {
					log.debug("Remote addresses didn't match " + hostAddress + " != " + host);
				}

                               
			for(IpSubnetFilterRule source : sources) {
                if(source.matches(remoteAddress)) {
                    return true;
                }
            }
    	}
              
		return false;
    };
}

当应用到配置的路由断言 WeightRoutePredicate 时,会根据 ServerWebExchange 中的 WEIGHT_ ATTR 值,判断当前的 routeId 与对应分组的 routeId 是否一致。

Filter

我们在上面解释了“pre””post”的作用,那么也可以看到filter的整个生命周期。

过滤器部分参考:https://www.cnblogs.com/bjlhx/p/9786478.html博客

Spring-Cloud-Gateway的过滤器接口分为两种:

  1. GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器
  2. GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上

过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。

下面是springCloud gateway 实现的filter列表

springCloud Finchley.RELEASE gateway 动态网关实现及gateway功能综述,优化_第3张图片

下面看看具体的过滤器实现

GatewayFilter

网关路由过滤器,与下述GatewayFilterChain一样只有一个filter接口,执行当前过滤器,并在此方法中决定过滤器链表是否继续往下执行。

springCloud Finchley.RELEASE gateway 动态网关实现及gateway功能综述,优化_第4张图片

它由上述几个过滤器实现。

  1. OrderedGatewayFilter:排序的网关路由过滤器,用于包装真实的网关过滤器,将目标过滤器包装成可排序的对象类型。是目标过滤器的包装类
  2. GatewayFilterAdapter:是一个适配器类,是web处理器(FilteringWebHandler)中的内部类。在网关过滤器链 GatewayFilterChain 中会使用 GatewayFilter 过滤请求,GatewayFilterAdapter的作用就是将全局过滤器 GlobalFilter 适配成 网关过滤器 GatewayFilter。
  3. ModifyResponseGatewayFilter 是 ModifyResponseBodyGatewayFilterFactory 的内部类,用于修改响应体的信息

 

GlobalFilter

为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,模式系统初始化时加载,并作用在每个路由上。通过GatewayAutoConfiguration自动创建初始化加载。

FilteringWebHandler:将全局过滤器包装成GatewayFilter,并作用于每个路由上

springCloud Finchley.RELEASE gateway 动态网关实现及gateway功能综述,优化_第5张图片

GatewayFilterChain: 用于过滤器的链式调用,只提供一个链表启动调用入口方法,过滤链表。

DefaultGatewayFilterChain:过滤器链表接口 GatewayFilterChain的默认实现,其中定义的List 为过滤器集合,以及执行过滤器在集合中的索引index。

由上述DefaultGatewayFilterChain可以看出GatewayFilterChain的执行顺序

  1. 通过GatewayFilter集合构建顶层的GatewayFilterChain
  2. 调用顶层GatewayFilterChain,获取第一个Filter,并创建下一个Filter索引对应的GatewayFilterChain
  3. 调用filter的filter方法执行当前filter,并将下次要执行的filter对应GatewayFilterChain传入。

Filter配置

这里只讲用到的filter

PrefixPathGatewayFilterFactory

针对请求url前缀进行处理的filter工厂,用户添加prefix。

spring:
  cloud:
    gateway:
      routes:
      - id: enn-dfs
        uri: lb://enn-dfs:6002
        filters:
        - PrefixPath=/file	
请求/upload,最后转发到目标服务的路径变为/file/upload	

StripPrefixGatewayFilterFactory

针对请求url前缀进行处理的filter工厂,用于去除prefix

spring:
  cloud:
    gateway:
      routes:
       - id: enn-dfs
        uri: lb://enn-dfs:6002
        predicates:
        - Path=/file/**
        filters:
        - StripPrefix=1
如请求路径为/file/upload去除掉前面1个前缀之后,最后转发到目标服务的路径为/upload

 

因为StripPrefix是直接操作Path,对于本版本来说,使用StripPrefix无效。

可以使用RewritePath,把映射的/file去除,可看下面配置。

通过RewritePath实现。代码查询Git源码。

"filters": [{
		"name": "RewritePath",
		"args": {
			"regexp": "/file/(?.*)",
			"replacement": "/${remaining}"
		}
	}]

 

 

 

如何动态加载路由

首先看如何加载的

Gateway 通过RouteDefinition来转换生成具体的路由信息,通过路由定义信息加载器RouteDefinitionLocator负责读取路由配置。看下子类实现。

  1. CachingRouteDefinitionLocator 缓存目标RouteDefinitionLocator 为routeDefinitions提供缓存功能
  2. CompositeRouteDefinitionLocator组合多种 RouteDefinitionLocator 的实现,为 routeDefinitions提供统一入口
  3. PropertiesRouteDefinitionLocator从配置文件(YML / Properties 等 ) 读取RouteDefinition
  4. DiscoveryClientRouteDefinitionLocator从注册中心(Eureka / Consul / Etcd 等 )读取RouteDefinition
  5. RouteDefinitionRepository 从存储器( 例如,内存 / Redis / MySQL 等 )读取RouteDefinition,我们的动态路由,也是在此类基础做扩展。

 

RouteDefinitionLocator接口有且仅有一个方法getRouteDefinitions,此方法获取RouteDefinition的核心方法,返回Flux

 

在这里要注意一个配置:spring.cloud.gateway.discovery.locator.enabled=true,它的启停比较关键,如果你想只加载自定义的路由配置,那么要把此配置设置为false。因为他会默认加载CompositeRouteDefinitionLocator(InMemoryRouteDefinitionRepository、PropertiesRouteDefinitionLocator、DiscoveryClientRouteDefinitionLocator三个RouteDefinitionLocator),把注册中心等由自动加载的路由都配置进去。

加载顺序

可以查看AutoConfiguration里面的Primary信息,就知道加载配置信息的顺序

PropertiesRouteDefinitionLocator-->|配置文件加载初始化

RouteDefinitionRepository-->|存储器中加载初始化

DiscoveryClientRouteDefinitionLocator-->|注册中心加载初始化

 

下面讲动态路由实现,由于时间关系,待续。

你可能感兴趣的:(分布式服务)