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

网友投稿 1877 2022-10-08

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

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

文章目录

​​一、前言​​​​二、结合Redis实现限流(RequestRateLimiterGatewayFilterFactory)​​

​​1、不指定KeyResolver的限流​​​​2、指定KeyResolver的限流​​

​​三、熔断​​

​​1、SpringCloudCircuitBreakerFilterFactory​​

​​1)针对所有的请求断路​​

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

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

​​2、FallbackHeadersGatewayFilterFactory​​

​​四、重试(RetryGatewayFilterFactory)​​​​五、总结​​

一、前言

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

​​【云原生&微服务>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版本信息:

2.4.2 2020.0.1 2021.1 org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import

二、结合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​​工作的。

org.springframework.boot spring-boot-starter-data-redis-reactive

否者会报错:

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​​)。

1、不指定KeyResolver的限流

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

示例:

redis: host: 127.0.0.1 port: 6379server: port: 9999spring: cloud: gateway: routes: - id: add_request_parameter_route uri: 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​​;

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 resolve(ServerWebExchange exchange) { // 根据请求IP来限流 return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); }}

2> 在yaml中配置KeyResolver

redis: host: 127.0.0.1 port: 6379server: port: 9999spring: cloud: gateway: routes: - id: add_request_parameter_route uri: 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​​。

org.springframework.cloud spring-cloud-starter-circuitbreaker-reactor-resilience4j

关于Spring Cloud CircuitBreaker,参考官方文章:​​自定义一个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 */@Configurationpublic class GatewayConfiguration { @Bean(name = "myCircuitBreaker") public Customizer 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: predicates: - Path=/** filters: -

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

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

已经可以正常响应。

当断路器生效、断路之后,我们想自定义它返回的信息,可以通过制定断路后的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;@RestControllerpublic 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: 9999spring: cloud: gateway: routes: - id: add_request_parameter_route uri: predicates: - Path=/** filters: - name: CircuitBreaker args: name: myCircuitBreaker # 指定断路后的重定向地址 fallbackUri: forward:/defaultFallback

当断路器断路后,会将请求重定向到当前Gateway服务的​​/defaultFallback​​(Body也变成了相应重定向后地址的响应体。

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

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

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

示例中将外部地址​​断路

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

server: port: 9999spring: cloud: gateway: routes: - id: add_request_parameter_route uri: 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​​接口的熔断器不会生效,依旧是直接报错。

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: predicates: - Path=/fallback # 通过过滤器将地址重写为:/hello/sayParam filters: - SetPath=/hello/sayParam - id: add_request_parameter_route uri: predicates: - Path=/** filters: - name: CircuitBreaker args: name: myCircuitBreaker # 指定断路后的重定向地址(route的方式) fallbackUri: forward:/fallback # 当且仅当出现如下状态码时,断路器才会生效 statusCodes: - 500 - "NOT_FOUND" - name: FallbackHeaders args: executionExceptionTypeHeaderName: Saint-Test-Header

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

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

四、重试(RetryGatewayFilterFactory)

Retry GatewayFilter factory支持以下参数:

​​retries​​: 重试次数;​​statuses​​​: 可以进行重试的响应的HTTP状态码, 使用​​org.springframework.可以进行重试的HTTP methods, 使用​​org.springframework.可以进行重试的The series of status codes,使用​​org.springframework.可以进行重试的Thrown Exceptions 列表;​​backoff​​:为重试配置指数级的重试时间间隔;在​​firstBackoff​​​ *(​​factor​​ ^ n)的重试间隔后执行重试,其中n是迭代(第几次重试);如果配置了​​maxBackoff​​​,则重试的最大时间间隔为​​maxBackoff​​;如果​​basedOnPreviousValue​​​被设置为true,则重试时间间隔使用​​prevBackoff * factor​​ 计算。

spring: cloud: gateway: routes: - id: add_request_parameter_route uri: 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

示例效果:

请求地址:​​Cloud Gateway基于​​RequestRateLimiterGatewayFilterFactory​​​实现限流、基于​​SpringCloudCircuitBreakerFilterFactory​​​实现熔断、基于​​RetryGatewayFilterFactory​​实现重试;

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信小程序之animation动画的实现(小程序的过渡动画如何实现)
下一篇:使用Monit搭建监控系统
相关文章

 发表评论

暂时没有评论,来抢沙发吧~