Spring Cloud Gateway 熔断、降级、限流

  • 熔断降级:在分布式系统中,网关作为流量的入口,大量请求进入网关,向后端远程系统或服务发起调用,后端服务不可避免的会产生调用失败(超时或者异常),失败时不能让请求堆积在网关上,需要快速失败并返回回去,这就需要在网关上做熔断、降级操作。
  • 限流:网关上有大量请求,对指定服务进行限流,可以很大程度上提高服务的可用性与稳定性,限流的目的是通过对并发访问/请求进行限速,或对一个时间窗口内的请求进行限速来保护系统。一旦达到限制速率则可以拒绝服务、排队或等待、降级。

Spring Cloud Gateway 集成熔断、限流

集成熔断

集成 Hystrix 熔断降级,引用hystrix依赖,在filters下加入熔断降级配置,设置降级后返回的路由,同时配置默认使用信号量隔离、3秒主动超时

1
2
3
4
5
<!-- 熔断、降级 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
filters:
# 熔断降级配置
- name: Hystrix
args:
name : default
fallbackUri: 'forward:/defaultfallback'

# hystrix 信号量隔离,3秒后自动超时
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 3000
shareSecurityContext: true

新建一个DefaultHystrixController 的RestController,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 默认降级处理
*/
@RestController
public class DefaultHystrixController {

@RequestMapping("/defaultfallback")
public Map<String,String> defaultfallback() {
System.out.println("降级操作...");
Map<String, String> map = new HashMap<>();
map.put("resultCode", "fail");
map.put("resultMessage", "服务异常");
map.put("resultObj", "null");
return map;
}
}

同时在consumer-serviceIndexController中添加一个timeout方法,让线程睡5秒,大于网关中设置超时的3秒,因此会触发熔断降级,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class IndexController {

@RequestMapping("/hello")
public String hello(String name) {
return "hi " + name;
}

@RequestMapping("/timeout")
public String timeout() {
try {
//睡5秒,网关Hystrix3秒超时,会触发熔断降级操作
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
return "timeout";
}
}

启动项目,访问 http://localhost:9999/consumer/timeout ,结果如下,触发了降级

1
{"resultObj":"null","resultCode":"fail","resultMessage":"服务异常"}

把网关的超时配置改为6000,也就是6秒,重启网关,再次访问 http://localhost:9999/consumer/timeout ,结果正常返回,网关上集成了 Hystrix 熔断降级,可以覆盖大部分后端服务不可用场景

注:也可在@FeignClient注解上使用fallback=Xxx.class参数实现默认熔断降级

1
2
3
4
5
6
@FeignClient(name = "consumer-service", fallback = HelloFeignFallbackService.class)
public interface HelloFeignService {

@RequestMapping(value = "/hello", method = RequestMethod.GET)
String hello(@RequestParam("name") String name);
}
1
2
3
4
5
6
7
8
9
10
/**
* hystrix服务降级处理,防止因超时、异常等导致的服务调用雪崩
*/
@Service
public class HelloFeignFallbackService implements HelloFeignService {
@Override
public String hello(String name) {
return "未找到" + name;
}
}

集成限流

Spring Cloud Gateway默认集成了Redis限流,可以对不同服务做不同维度的限流,如:IP限流、用户限流 、接口限流

本文演示的是 IP限流 ,先添加redis依赖,添加KeyResolver,再添加配置,需启动redis

1
2
3
4
5
<!-- 限流 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

通过KeyResolver来指定限流的Key,新建一个 RateLimiterConfig 类,IP限流代码如下:

1
2
3
4
5
6
7
8
9
10
11
/**
* 路由限流配置
*/
@Configuration
public class RateLimiterConfig {

@Bean(value = "remoteAddrKeyResolver")
public KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}

application.yml下添加限流配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
redis:
host: localhost
filters:
# redis限流,filter名称必须是RequestRateLimiter
- name: RequestRateLimiter
args:
# 使用SpEL名称引用Bean,与上面新建的RateLimiterConfig类中的bean的name相同
key-resolver: '#{@remoteAddrKeyResolver}'
# 每秒最大访问次数
redis-rate-limiter.replenishRate: 20
# 令牌桶最大容量
redis-rate-limiter.burstCapacity: 20
  • filter名称必须是RequestRateLimiter
  • redis-rate-limiter.replenishRate:允许用户每秒处理多少个请求
  • redis-rate-limiter.burstCapacity:令牌桶的容量,允许在一秒钟内完成的最大请求数
  • key-resolver:使用SpEL按名称引用bean

测试网关是否做到了限流,使用 jmeter 测试工具,测试配置如下,100个线程,2秒内,循环2次,总共200个请求,请求地址:http://localhost:9999/consumer/hello?name=joe

(1)第一次设置限流数 2、10

1
2
redis-rate-limiter.replenishRate: 2
redis-rate-limiter.burstCapacity: 10

测试结果如下,只有14个请求响应回去,其他全被拒绝了

(2)第二次设置限流数 20、20

1
2
redis-rate-limiter.replenishRate: 20
redis-rate-limiter.burstCapacity: 20

测试结果如下,有大量请求进行了响应,部分拒绝

(3)还可以使用用户限流、接口限流

用户限流,使用这种方式限流,请求路径中必须携带userId参数

1
2
3
4
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}

接口限流,获取请求地址的uri作为限流key

1
2
3
4
@Bean
KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}

在真实场景中,限流数的调整需要依赖配置中心,当网站做活动时,动态调整限流数,新服务上线时,通过配置中心做动态路由等

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2021 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :