文章目录

一、前言

至此微服务网关系列文章已出:

  1. ​【云原生&微服务>SCG网关篇一】为什么要有网关、生产环境如何选择网关​
  2. ​云原生&微服务>SCG网关篇二】生产上那些灰度发布方式​
  3. ​【云原生&微服务>SCG网关篇三】Spring Cloud Gateway是什么、详细使用案例​
  4. ​云原生&微服务>SCG网关篇四】Spring Cloud Gateway内置的11种PredicateFactory如何使用​
  5. ​【云原生&微服务>SCG网关篇五】Spring Cloud Gateway自定义PredicateFactory​
  6. ​【云原生&微服务>SCG网关篇六】Spring Cloud Gateway内置的18种Filter使用姿势​

聊了以下问题:

  1. 为什么要有网关?网关的作用是什么?
  2. 网关的分类?
  3. 网关的技术选型?
  4. 使用网关时常用的灰度发布方式有哪些?
  5. Spring Cloud Gateway是什么?详细使用案例?
  6. Spring Cloud Gateway内置的11种PredicateFactory
  7. 如何自定义PredicateFactory?
  8. Spring Cloud Gateway内置的18种常用的Filter

本文接着聊Spring Cloud Gateway基于内置Filter如何实现限流、熔断、重试。

PS:SpringCloud版本信息:

<properties>
<spring-boot.version>2.4.2</spring-boot.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

二、结合Redis实现限流(RequestRateLimiterGatewayFilterFactory)

Spring Cloud Gateway可以使用​​RequestRateLimiter GatewayFilter factory​​​结合Redis基于令牌桶算法实现限流功能;如果请求无法通过限流,则会返回​​HTTP 429 - Too Many Requests​​;

RequestRateLimiter 采用可选的​​keyResolver​​参数和特定于速率限制器的参数;

​keyResolver​​​参数是实现KeyResolver接口的Bean,KeyResolver接口让 可插入策略 派生出限制请求的密钥;
KeyResolver的默认实现是​​​PrincipalNameKeyResolver​​​,它从ServerWebExchange中检索出​​Principal​​并调用Principal.getName()。

结合Redis实现限流需要在gateway项目中引入​​spring-boot-starter-data-redis-reactive​​​的Spring Boot starter,因为Redis的实现基于​​Stripe​​工作的。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

否者会报错:

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_java

RequestRateLimiter速率限制器的参数如下:

  1. ​redis-rate-limiter.replenishRate​​,在不丢弃请求的情况下,允许用户每秒执行的请求数,即:每秒令牌桶的填充速率
  2. ​redis-rate-limiter.burstCapacity​​,表示允许用户在一秒钟内执行的最大请求数,即:令牌桶可以容纳的令牌数;将此值设置为零会阻止所有请求。
  3. ​redis-rate-limiter.requestedTokens​​,表示一个请求需要多少个令牌,即:针对每个请求从令牌桶提取的令牌数,默认为1。

通过设置​​redis-rate-limiter.replenishRate​​​ 和 ​​redis-rate-limiter.burstCapacity​​相等可以实现稳定限流速率。

如果将​​redis-rate-limiter.burstCapacity​​​设置为高于 ​​redis-rate-limiter.replenishRate​​,则允许临时的挤满 / 冲突

  • 这种情况下,速率限制器(​​rate limiter​​​)会在两次挤满 / 突发之间留出一段时间(根据令牌桶填充速率决定速率,比如:填充速率是5,令牌最大数是10,则富余5个令牌,可以临时顶1s),因为两次连续的挤满 / 冲突发会导致请求丢失(​​HTTP 429 - Too Many Requests​​)。

1、不指定KeyResolver的限流

结合Redis实现的限流,当不指定​​KeyResolver​​​参数时,会采用​​KeyResolver​​的默认实现,对所有请求进行限流;

示例:

redis:
host: 127.0.0.1
port: 6379

server:
port: 9999

spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
# 请求数限流 名字不能随便写 ,使用默认的factory,默认使用令牌桶算法
- name: RequestRateLimiter
args:
# 针对请求IP进行限流
# key-resolver: "#{@ipKeyResolver}"
# 在不丢弃请求的情况下,允许用户每秒执行的请求数,即令牌桶填充速率:1/s(一秒放一个)
redis-rate-limiter.replenishRate: 1
# 表示允许用户在一秒钟内执行的最大请求数,即:令牌桶可以容纳的令牌数;将此值设置为零会阻止所有请求
redis-rate-limiter.burstCapacity: 1
# 表示一个请求需要多少个令牌,默认为1
redis-rate-limiter.requestedTokens: 1

这里表示对所有的请求,每秒仅允许一个请求过去,其余的请求都会被拦截,报错:​​HTTP 429 - Too Many Requests​​;

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_spring_02

2、指定KeyResolver的限流

KeyResolver接口主要用于设置限流请求的key;我们可以通过指定KeyResolver将请求进行分批限流,比如针对同一个IP的请求做限流;

1> 传建一个KeyResolver接口的实现类:IpKeyResolver

package com.saint.gateway.filter;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
* KeyResolver接口主要用于设置限流请求的key,通过实现该接口指定需要对当前请求中对哪些因素进行流量控制。
*/
@Component("ipKeyResolver")
public class IpKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
// 根据请求IP来限流
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}

2> 在yaml中配置KeyResolver

redis:
host: 127.0.0.1
port: 6379

server:
port: 9999

spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
# - Path=/gateway/**
filters:
# 请求数限流 名字不能随便写 ,使用默认的factory,默认使用令牌桶算法
- name: RequestRateLimiter
args:
# 针对请求IP进行限流
key-resolver: "#{@ipKeyResolver}"
# 在不丢弃请求的情况下,允许用户每秒执行的请求数,即令牌桶填充速率:1/s(一秒放一个)
redis-rate-limiter.replenishRate: 1
# 表示允许用户在一秒钟内执行的最大请求数,即:令牌桶可以容纳的令牌数;将此值设置为零会阻止所有请求
redis-rate-limiter.burstCapacity: 1
# 表示一个请求需要多少个令牌,默认为1
redis-rate-limiter.requestedTokens: 1

三、熔断

1、SpringCloudCircuitBreakerFilterFactory

Spring Cloud Gateway可以使用​​CircuitBreaker GatewayFilter factory​​​基于​​Spring Cloud CircuitBreaker APIs​​​在断路器中包裹网关路由;​​Spring Cloud CircuitBreaker​​​中存在很多支持​​Spring Cloud Gateway​​​使用的库,比如:开箱即用的​​Resilience4J​​。

和RequestRateLimiter结合Redis实现限流需要引入​​spring-boot-starter-data-redis-reactive​​​一样,​​CircuitBreaker​​​需要引入​​spring-cloud-starter-circuitbreaker-reactor-resilience4j​​。

<!--熔断、降级-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

关于Spring Cloud CircuitBreaker,参考官方文章:​​https://cloud.spring.io/spring-cloud-circuitbreaker/reference/html/spring-cloud-circuitbreaker.html​​。

1)针对所有的请求断路

1> 自定义一个ReactiveResilience4JCircuitBreakerFactory:

package com.saint.gateway.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
* @author Saint
*/
@Configuration
public class GatewayConfiguration {

@Bean(name = "myCircuitBreaker")
public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
// 超时规则,默认1s,这里是3秒
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build())
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
.build());
}
}

断路器​​CircuitBreaker​​中配置了路由的超时时间为3s,默认为1s。

2> 在application.yml中指定过滤器Filter:

spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
-

访问​​Spring Cloud Gateway详细使用案例​​​中的​​/hello/timeout​​接口,其中线程睡眠了5s,所以一定会超时。

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_微服务_03

到这里超时的场景已经验证完毕,再修改​​CircuitBreaker​​的配置,将超时时间修改为6s,再访问一下接口:

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_云原生_04

已经可以正常响应。

当断路器生效、断路之后,我们想自定义它返回的信息,可以通过制定断路后的fallbackURI实现。

指定断路后的fallbackURI(gateway内部)

Spring Cloud CircuitBreaker filter可以指定一个可选择的参数​​fallbackUri​​,用于指定断路后重定向的URI。重定向的URI可以是Gateway项目内部的Controller、Route,也可以是外部的链接。

1> 在Gateway项目中新增一个Controller:

package com.saint.gateway.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class FallbackController {
@RequestMapping("/defaultFallback")
public Map defaultFallback() {
Map map = new HashMap<>();
map.put("code", 999);
map.put("message", "server error");
return map;
}
}

2> 在application.yml文件中指定fallbackURI:

server:
port: 9999
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
# 指定断路后的重定向地址
fallbackUri: forward:/defaultFallback

当断路器断路后,会将请求重定向到当前Gateway服务的​​/defaultFallback​​(http://127.0.0.1:9999/defaultFallback)下。

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_云原生_05

由于请求已经被重定向,所以返回的状态码是​​200​​​,而不再是​​504​​,Response Body也变成了相应重定向后地址的响应体。

指定断路后的fallbackURI(gateway外部)

我们也可以通过将外部地址配置到Route的方式,进而fallback到路由上,实现短路后fallback重定向到外部链接的需求。

server:
port: 9999
spring:
cloud:
gateway:
routes:
- id: ingredients-fallback
uri: http://127.0.0.1:9001
predicates:
- Path=/fallback
# 通过过滤器将地址重写为:/hello/sayParam
filters:
- SetPath=/hello/sayParam
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
# 指定断路后的重定向地址(route的方式)
fallbackUri: forward:/fallback

示例中将外部地址​​http://127.0.0.1:9001//hello/sayParam​​​配置到Path为​​/fallback​​​的Route上,并且在断路中配置fallbackUri为:​​forward:/fallback​​​;即当断路器断路时,fallback地址为:​​http://127.0.0.1:9001//hello/sayParam​​。

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_spring_06

2)针对返回的状态码 断路

在某些情况下,我们可能希望根据Route返回的状态码使断路器跳闸;断路器配置对象拥有一个状态码的集合,如果路由返回的状态码在集合中,则断路器跳闸;状态码可以使用代表码值的整数 或 HttpStatus枚举的字符串。

server:
port: 9999

spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
# 指定断路后的重定向地址(gateway内部controller)
fallbackUri: forward:/defaultFallback
# 当且仅当出现如下状态码时,断路器才会生效
statusCodes:
- 500
- "NOT_FOUND"

当路由返回的状态码为500、或404(“NOT_FOUND”)时会触发断路器,其余状态码均不会。

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_java_07

​/hello/retryRoute​​接口会中会直接报错,返回500,断路器将500的状态码进行了拦截。

如果不配置statusCodes,那么针对/hello/retryRoute接口的熔断器不会生效,依旧是直接报错。

2、FallbackHeadersGatewayFilterFactory

FallbackHeaders factory 可以将一些​​Spring Cloud CircuitBreaker​​执行的异常细节添加到fallbackUri的请求头中,请求头中的属性可以包括:

  • ​executionExceptionTypeHeaderName​​ (“Execution-Exception-Type”)
  • ​executionExceptionMessageHeaderName​​ (“Execution-Exception-Message”)
  • ​rootCauseExceptionTypeHeaderName​​ (“Root-Cause-Exception-Type”)
  • ​rootCauseExceptionMessageHeaderName​​ (“Root-Cause-Exception-Message”)
spring:
cloud:
gateway:
routes:
- id: ingredients-fallback
uri: http://127.0.0.1:9001
predicates:
- Path=/fallback
# 通过过滤器将地址重写为:/hello/sayParam
filters:
- SetPath=/hello/sayParam
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
# 指定断路后的重定向地址(route的方式)
fallbackUri: forward:/fallback
# 当且仅当出现如下状态码时,断路器才会生效
statusCodes:
- 500
- "NOT_FOUND"
- name: FallbackHeaders
args:
executionExceptionTypeHeaderName: Saint-Test-Header

访问:​​http://127.0.0.1:9999/hello/retryRoute​​​,debug ​​fateway-center​​​项目,可以发现请求进了​​FallbackHeadersGatewayFilterFactory​​​,不过由于​​FallbackHeadersGatewayFilterFactory​​​中无法从​​ServerWebExchange​​​中获取到的​​circuitBreakerExecutionException​​属性值为null,所以没有异常信息记录到fallbackUri的请求头中;

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_java_08

从上图的代码逻辑也可以看出,只有当​​circuitBreakerExecutionException​​属性有值时,才会将异常信息记录到fallbackUri的请求头中。

四、重试(RetryGatewayFilterFactory)

Retry GatewayFilter factory支持以下参数:

  1. ​retries​​: 重试次数;
  2. ​statuses​​​: 可以进行重试的响应的HTTP状态码, 使用​​org.springframework.http.HttpStatus​​表示;
  3. ​methods​​​: 可以进行重试的HTTP methods, 使用​​org.springframework.http.HttpMethod​​表示;
  4. ​series​​​: 可以进行重试的The series of status codes,使用​​org.springframework.http.HttpStatus.Series​​表示;
  5. ​exceptions​​: 可以进行重试的Thrown Exceptions 列表;
  6. ​backoff​​:为重试配置指数级的重试时间间隔;
  • 在​​firstBackoff​​​ *(​​factor​​ ^ n)的重试间隔后执行重试,其中n是迭代(第几次重试);
  • 如果配置了​​maxBackoff​​​,则重试的最大时间间隔为​​maxBackoff​​;
  • 如果​​basedOnPreviousValue​​​被设置为true,则重试时间间隔使用​​prevBackoff * factor​​ 计算。
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: http://127.0.0.1:9001
predicates:
- Path=/**
filters:
# 重试
- name: Retry
args:
# 请求重试次数,默认值是3
retries: 3
# 可以进行重试的状态码(500、404),
statuses: INTERNAL_SERVER_ERROR,NOT_FOUND
# 可以进行重试的Http Method
methods: GET,POST
# 重试的时间间隔配置
backoff:
# 第一次重试的时间间隔
firstBackoff: 100ms
# 最大重试时间间隔
maxBackoff: 500ms
# 时间间隔因子
factor: 2
# 关闭根据上次重试时间间隔计算当前重试时间间隔功能
basedOnPreviousValue: false

示例效果:

请求地址:​​http://127.0.0.1:9999/hello/retryRoute​​;

服务的日志输出:

【云原生&微服务>SCG网关篇七】Spring Cloud Gateway基于内置Filter实现限流、熔断、重试_java_09

第一次请求失败后,100ms进行了一次重试,再200ms之后进行了一次重试,再400ms之后进行了一次重试;一共重试三次。

五、总结

本文聊了Spring Cloud Gateway基于​​RequestRateLimiterGatewayFilterFactory​​​实现限流、基于​​SpringCloudCircuitBreakerFilterFactory​​​实现熔断、基于​​RetryGatewayFilterFactory​​实现重试;

另外本文相关案例全部来自:​​【云原生&微服务>SCG网关篇三】Spring Cloud Gateway是什么、详细使用案例​​。