Spring Cloud Gateway原理
阅读原文时间:2021年11月19日阅读:1

1.使用

compile 'org.springframework.cloud:spring-cloud-starter-gateway'

2.包结构

actuate中定义了一个叫GatewayControllerEndpoint的类,这个类提供一些对外的接口,可以获取网关的一些信息,比如路由的信息,改变路由地址等等

config中定义了一些启动时去加载的类,配置路由信息和读取你的配置文件就在这里完成

discovery中定义了注册中心相关的内容,包括注册中心的路由等

event定义了一些事件他们都继承自ApplicationEvent,对事件发布不了解的可以去看看spring的代码

filter中定义了spring cloud gateway实现的一些过滤器

handler中定义了很多Predicate相关的Factory

route就是我们路由的相关

support是工具包

3.启动流程

网关启动第一步加载的就是去加载config包下的几个类

1.> GatewayClassPathWarningAutoConfiguration.class:  如果DispatcherServlet存在,会给与警告,同样的DispatcherHandler不存在也会警告。

注意不要引入spring-boot-starter-web的依赖,会报错,因为gateway是基于spring-webflux开发的,他依赖的DispatcherHandler就和web里的DispatcherServlet是一样的功能

2.> GatewayLoadBalancerClientAutoConfiguration.class:  gateway负载均衡的过滤器实现的加载,他将LoadBalancerClientFilter 注入到了容器中

3.> GatewayAutoConfiguration.class: `注册始初始化配置文件加载器、路由````转发、过滤器、```心跳监测

4.> GatewayDiscoveryClientAutoConfiguration.class 注册DiscoveryClientRouteDefinitionLocator.class,在构造方法中通过discoveryClient获取服务发现中心的服务路由(存疑:容器启动时只有当前服务的路由信息,而不是所有服务路由信息)

5.> RouteDefinitionRouteLocator.class实现RouteLocator接口(内部是使用PropertiesRouteDefinitionLocator.class)读取GatewayProperties配置文件转成路由RouteDefinition

6.> DiscoveryClientRouteDefinitionLocator.class实现RouteDefinitionLocator接口,getRouteDefinitions()时,通过discoveryClient转换服务发现中心的服务路由

4.工作原理

客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。

该处理程序通过特定于请求的过滤器链运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。

所有“前置”过滤器(Pre Filter)逻辑均被执行。然后发出代理请求。发出代理请求后,将运行“后”过滤器(Post Filter)逻辑。

执行顺序类似栈LIFO(后进先出),U型顺序。

HttpWebHandlerAdapter->RoutePredicateHandlerMapping->FilteringWebHandler

5.谓词/断言

predicates Factory

配置

描述

After

- After=2017-01-20T17:42:47.789-07:00[America/Denver]

在该日期时间之后发生的请求都将被匹配。

Before

- Before=2017-01-20T17:42:47.789-07:00[America/Denver]

在该日期时间之前发生的请求都将被匹配。

Between

- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

在两个日期时间之间发生的请求都将被匹配。

Cookie

- Cookie=chocolate, ch.p

请求包含次cookie名称且正则表达式为真的将会被匹配。

Header

- Header=X-Request-Id, \d+

请求包含次header名称且正则表达式为真的将会被匹配。

Host

- Host=**.somehost.org,**.anotherhost.org

使用Ant路径匹配规则,.作为分隔符

Method

- Method=GET

所有GET请求都将被路由

Path

- Path=/foo/{segment},/bar/{segment}

路径/foo/开头或/bar/开头的请求都将被匹配

Query

- Query=baz

- Query=foo, ba.

包含了请求参数 baz的都将被匹配

请求参数里包含foo参数,并且值匹配为ba.

RemoteAddr

- RemoteAddr=192.168.1.1/24

请求的remote address 为 192.168.1.10则将被路由

Weight

routes:

 - id: weight_high

  uri: https://weighthigh.org

  predicates:

    - Weight=group1, 8

 - id: weight_low

  uri: https://weightlow.org

  predicates:

    - Weight=group1, 2

将大约80%的流量转发到weighthigh.org,将大约20%的流量转发到weightlow.org

6.过滤器

  1. 全局过滤器与其他2类过滤器相比,永远是最后执行的;它的优先级只对其他全局过滤器起作用
  2. 当默认过滤器与自定义过滤器的优先级一样时,优先出发默认过滤器,然后才是自定义过滤器;同类型的过滤器,出发顺序与他们在配置文件中声明的顺序一致
  3. 默认过滤器与自定义过滤器使用同样的order顺序空间,即他们会按照各自的顺序来进行排序

自定义过滤器:
public class CustomGatewayFilter implements GatewayFilter, Ordered

自定义全局过滤器:

public class CustomGlobalFilter implements GlobalFilter, Ordered

GatewayFilter Factory

配置

描述

 AddRequestHeader

filters:

  - AddRequestHeader=X-Request-Foo, Bar

 向下游请求的header头中添加 x-request-foo:bar

 AddRequestParameter

 - AddRequestParameter=foo, bar

 向下游请求添加foo=bar请求参数

 AddResponseHeader

 - AddResponseHeader=X-Response-Foo, Bar

 向下游响应的header头添加x-response-foo:bar

 CircuitBreaker

- Hystrix=myCommandName

或者

filters:

  - name: CircuitBreaker

   args:

    name: myCircuitBreaker

    fallbackUri: forward:/inCaseOfFailureUseThis

  - RewritePath=/consumingServiceEndpoint, /backingServiceEndpoint

断路器过滤器;发生断路时将请求转发或者调用fallback处理

还可以将请求重新路由到外部应用程序中的控制器或处理程序

 PrefixPath

 - PrefixPath=/mypath

 所有匹配请求的路径加前缀/mypath。因此,向/hello发送的请求将发送到/mypath/hello

 RequestRateLimiter

filters:

  - name: RequestRateLimiter

  args:

    redis-rate-limiter.replenishRate: 10

    redis-rate-limiter.burstCapacity: 20

    redis-rate-limiter.requestedTokens: 1

令牌桶限流

每个用户10的请求速率限制。

允许20个突发,

但是在下一秒中,只有10个请求可用

 RedirectTo

 - RedirectTo=302, http://acme.org

 发送一个302状态码和一个Location:http://acme.org header来执行重定向

 RemoveRequestHeader

 - RemoveRequestHeader=X-Request-Foo

 向下游请求的header头中删除X-Request-Foo

 RemoveResponseHeader

 - RemoveResponseHeader=X-Request-Foo

 向下游响应的header头中删除X-Request-Foo

 RewritePath

 - RewritePath=/foo/(?.*), /$\{segment}

 使用Java正则表达式重写请求路径

 RewriteResponseHeader

 - RewriteResponseHeader=X-Response-Foo, , password=[^&]+, password=***

 使用Java正则表达式重写响应头的值

 SetPath

predicates:

  - Path=/foo/{segment}

filters:

  - SetPath=/{segment}

 对于一个 /foo/bar请求,在做下游请求前,路径将被设置为/bar

 SetResponseHeader

 同上

  同上

 SetStatus

 同上

  同上

 StripPrefix

predicates:

  - Path=/name/**

filters:

  - StripPrefix=1

 当通过网关发出/name/bar/foo请求时,向nameservice发出的请求将是http://nameservice/bar/foo

 Retry

filters:
  - name: Retry
args:
  retries: 3
  statuses: BAD_GATEWAY

 retry filter 不支持body请求的重试,如通过body的POST 或 PUT请求

 RequestSize

filters:

  - name: RequestSize

args:

  maxSize: 5000000

 请求大小大于限制

负载均衡

spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 启用自动根据服务ID生成路由
lower-case-service-id: true # 设置路由的路径为小写的服务ID
routes:
- id: sso-service # 路由ID(一个路由配置一个ID)
uri: lb://sso # 通过注册中心来查找服务(lb代表从注册中心获取服务,并且自动开启负载均衡
predicates:
- Path=/auth/** # 匹配到的以/product开头的路径都转发到product的服务,相当于访问 lb://PRODUCT-SERVICE/**
filters:
- StripPrefix=1 # 去掉匹配到的路径的第一段

LoadBalancerClientFilter :实现负载均衡的全局过滤器,内部实现是ribbon

权重负载

routes:
- id: spring-cloud-client-demo
uri: lb://spring-cloud-client-demo
predicates:
- Path=/client/** - Weight=group1, 2
filters:
- StripPrefix=1
- id: spring-cloud-client-demo1
uri: lb://spring-cloud-client-demo
predicates:
- Path=/client/**
- Weight=group1, 8
filters:
- StripPrefix=1

限流

默认:RequestRateLimiterGatewayFilterFactory限流过滤器和限流的实现类RedisRateLimiter使用令牌桶限流;

常用的限流算法有几种:计数器算法、漏桶算法令牌桶算法

·计数算法适合流量突发情况(瞬间突发)

·令牌桶适合均速,无法获取令牌的请求直接拒绝

·漏桶算法适合均速并且可以让请求进行等待,不需要直接拒绝请求

计数器算法:

维护一个单位时间内的计数器(例如:设置1s内允许请求次数10次),表示为时间单位1秒内允许计数次数最高为10,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值(10),则之后的请求都被拒绝,直到单位时间(1s)已经过去,再将计数器重置为零,缺点:如果在单位时间1s内允许100个请求,在10ms已经通过了100个请求,那后面的990ms所接收到的请求都会被拒绝,我们把这种现象称为“突刺现象”。

漏桶算法:

水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。

令牌桶算法:

随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。

自定义限流过滤:

public class UriKeyResolver implements KeyResolver {

@Override  
public Mono<String> resolve(ServerWebExchange exchange) {  
    return Mono.just(exchange.getRequest().getURI().getPath());  
}

}
//将这个类的Bean注册到Ioc容器中
@Bean
public UriKeyResolver uriKeyResolver() {
return new UriKeyResolver();
}

或者

//自定义限流
@Bean
public KeyResolver hostAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}

重试和熔断参考限流,不再赘述