本地搭建SpringCloud Demo引入API Gateway模块,记录一些SpringCloud Gateway相关的知识。

一、Gateway

首先看一下Spring官网的微服务架构图:

可以看待网关位于所有服务的上层,所有服务的调用都需要经过网关。

Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

Spring Cloud Gateway是基于异步非阻塞模型上进行开发,性能优秀,且具有动态路由、对路由指定Predicate和Filter、集成Hystrix的断路器功能、集成SpringCloud服务发现、请求限流、支持路由重写等特性。

Zuul 1.x以及过时,且其基于阻塞I/O实现,性能较差,Zuul 2.x咕咕咕,且没有和SpringCloud进行整合…

二、Spring Cloud Gateway

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

网关的工作流程如下:

Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.

首先客户端向网关发请求,网关根据路由映射匹配相应的路由,然后发给Gateway Web Handler,其通过指定的过滤器链将请求发到实际的服务执行逻辑,最后返回。其中的Filter过滤器可以在之前或之后执行,如pre过滤器可以进行参数校验、权限校验、流量监控、日志输出、协议转换等,post过滤器可以响应内容、响应头的修改,日志的输出,流量监控等。

三、本地基础配置

路由的配置有两种方法,yml文件或者config配置类,下面是yml配置的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
application:
name: cloud-gateway
# ---------------------网关配置开始---------------------
cloud:
gateway:
discovery:
locator:
enabled: true # 动态路由,根据微服务名称
routes:
- id: payment_route # 路由id,唯一
uri: lb://cloud-payment-service # 匹配后的路由地址
predicates:
- Path=/payment/get/** # 断言
- id: payment_route2
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/**

根据微服务的名称cloud-payment-service进行动态路由,通过这种方式可以实现对服务的负载均衡,如下图可以看到轮询访问8001和8002服务。

四、网关熔断和限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
spring:
application:
name: cloud-gateway
# ---------------------网关配置开始---------------------
cloud:
gateway:
discovery:
locator:
enabled: true # 动态路由,根据微服务名称
lower-case-service-id: true # 服务名称转小写
routes:
- id: payment_route # 路由id,唯一
uri: lb://cloud-provider-hystrix-payment # 匹配后的路由地址 CLOUD-PROVIDER-HYSTRIX-PAYMENT
predicates:
- Path=/payment/hystrix/ok/**, /payment/hystrix/timeout/**, /payment/hystrix/error/ # 断言
filters:
# 重试机制
- name: Retry
args:
retries: 3
series: SERVER_ERROR
methods: GET,POST
# 网关限流
- name: Hystrix
args:
name: localfallback
fallbackUri: forward:/gateway/defaultfallback
- name: RequestRateLimiter
args:
key-resolver: '#{@remoteAddrKeyResolver}'
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 3 #令牌桶容量
redis-rate-limiter.requestedTokens: 1 # 每次消耗令牌个数
# ---------------------网关配置结束---------------------
redis:
host: localhost
port: 6379
database: 0

在本地添加对网关的熔断和限流。熔断使用的仍然是Hystrix,当下游服务宕机时,调用同一定义的处理逻辑defaultfallback。

1
2
3
4
5
6
7
8
@RestController
public class FallbackController {

@GetMapping(value = "/gateway/defaultfallback")
public String defaultFallback() {
return "defaultFallback:路由熔断.......";
}
}

网关的限流采用的Redis的令牌桶,并定义了用于限流的键的解析器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@Slf4j
public class RemoteAddrKeyResolver implements KeyResolver {

@Override
public Mono<String> resolve(ServerWebExchange exchange) {
// 根据IP地址限流
String hostAddress = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
log.info("hostAddress = " + hostAddress);

// 根据请求地址限流
String uriPath = exchange.getRequest().getURI().getPath();
log.info("uriPath = " + uriPath);

return Mono.just(hostAddress);
}
}

我们用Apifox做个简单的测试,两个线程循环调用http://localhost:9527/payment/hystrix/ok/1这个地址,结果如下,可以看到部分调用由于访问频繁被限流,其状态码对应为429。