至此微服务网关系列文章已出:
- 【云原生&微服务>SCG网关篇一】为什么要有网关、生产环境如何选择网关
- 云原生&微服务>SCG网关篇二】生产上那些灰度发布方式
- 【云原生&微服务>SCG网关篇三】Spring Cloud Gateway是什么、详细使用案例
- 云原生&微服务>SCG网关篇四】Spring Cloud Gateway内置的11种PredicateFactory如何使用
- 【云原生&微服务>SCG网关篇五】Spring Cloud Gateway自定义PredicateFactory
- 【云原生&微服务>SCG网关篇六】Spring Cloud Gateway内置的18种Filter使用姿势
聊了以下问题:
- 为什么要有网关?网关的作用是什么?
- 网关的分类?
- 网关的技术选型?
- 使用网关时常用的灰度发布方式有哪些?
- Spring Cloud Gateway是什么?详细使用案例?
- Spring Cloud Gateway内置的11种PredicateFactory
- 如何自定义PredicateFactory?
- Spring Cloud Gateway内置的18种常用的Filter
本文接着聊Spring Cloud Gateway基于内置Filter如何实现限流、熔断、重试。
PS:SpringCloud版本信息:
<properties>
<spring-boot.version>2.4.2spring-boot.version>
<spring-cloud.version>2020.0.1spring-cloud.version>
<spring-cloud-alibaba.version>2021.1spring-cloud-alibaba.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
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.bootgroupId>
<artifactId>spring-boot-starter-data-redis-reactiveartifactId>
dependency>
RequestRateLimiter速率限制器的参数如下:
redis-rate-limiter.replenishRate
,在不丢弃请求的情况下,允许用户每秒执行的请求数,即:每秒令牌桶的填充速率。redis-rate-limiter.burstCapacity
,表示允许用户在一秒钟内执行的最大请求数,即:令牌桶可以容纳的令牌数;将此值设置为零会阻止所有请求。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
)。结合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
;
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
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.cloudgroupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4jartifactId>
dependency>
关于Spring Cloud CircuitBreaker,参考官方文章:https://cloud.spring.io/spring-cloud-circuitbreaker/reference/html/spring-cloud-circuitbreaker.html。
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:
- CircuitBreaker=myCircuitBreaker
访问Spring Cloud Gateway详细使用案例中的/hello/timeout
接口,其中线程睡眠了5s,所以一定会超时。
到这里超时的场景已经验证完毕,再修改CircuitBreaker
的配置,将超时时间修改为6s,再访问一下接口:
已经可以正常响应。
当断路器生效、断路之后,我们想自定义它返回的信息,可以通过制定断路后的fallbackURI实现。
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)下。
由于请求已经被重定向,所以返回的状态码是200
,而不再是504
,Response Body也变成了相应重定向后地址的响应体。
我们也可以通过将外部地址配置到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
。
在某些情况下,我们可能希望根据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”)时会触发断路器,其余状态码均不会。
/hello/retryRoute
接口会中会直接报错,返回500,断路器将500的状态码进行了拦截。
如果不配置statusCodes,那么针对/hello/retryRoute
接口的熔断器不会生效,依旧是直接报错。
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的请求头中;
从上图的代码逻辑也可以看出,只有当circuitBreakerExecutionException
属性有值时,才会将异常信息记录到fallbackUri的请求头中。
Retry GatewayFilter factory支持以下参数:
retries
: 重试次数;statuses
: 可以进行重试的响应的HTTP状态码, 使用org.springframework.http.HttpStatus
表示;methods
: 可以进行重试的HTTP methods, 使用org.springframework.http.HttpMethod
表示;series
: 可以进行重试的The series of status codes,使用org.springframework.http.HttpStatus.Series
表示;exceptions
: 可以进行重试的Thrown Exceptions 列表;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
;
第一次请求失败后,100ms进行了一次重试,再200ms之后进行了一次重试,再400ms之后进行了一次重试;一共重试三次。
本文聊了Spring Cloud Gateway基于RequestRateLimiterGatewayFilterFactory
实现限流、基于SpringCloudCircuitBreakerFilterFactory
实现熔断、基于RetryGatewayFilterFactory
实现重试;
另外本文相关案例全部来自:【云原生&微服务>SCG网关篇三】Spring Cloud Gateway是什么、详细使用案例。