一、熔断降级
1.1 为什么要实现熔断降级?
在分布式系统中,网关作为流量的入口,因此会有大量的请求进入网关,向其他服务发起调用,其他服务不可避免的会出现调用失败(超时、异常),失败时不能让请求堆积在网关上,需要快速失败并返回给客户端,想要实现这个要求,就必须在网关上做熔断、降级操作。
1.2 基于 hystrix 熔断降级
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 配置
server:
# 配置应用端口
port: 8080
spring:
application:
# 配置应用名称
name: gateway
cloud:
nacos:
discovery:
# 注册服务中心地址
server-addr: 192.168.205.10:8848
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
# 配置 Hystrix
- name: Hystrix
args:
name: fallbackCmdA
# 降级调用 URI
fallbackUri: forward:/fallbackA
# 设置超时时间,单位:ms
hystrix.command.fallbackCmdA.execution.isolation.thread.timeoutInMilliseconds: 5000
- 创建降级回调方法
@RestController
public class FallbackController {
@GetMapping("/fallbackA")
public String fallbackA() {
return "服务暂时不可用";
}
}
- 启动 OrderService 和 gateway 服务,并访问 http://localhost:8080/order/create 返回:
订单创建成功
- 关闭OrderService 访问 http://localhost:8080/order/create 返回:
服务暂时不可用
证明熔断降级已生效。
二、限流
2.1 为什么需要限流?
- 防止大量的请求使服务器过载,导致服务不可用
- 防止网络攻击
2.2 常见的限流算法
计数器算法
在指定时间内对请求数做累计,当数量大于设置的值时,后续的请求都将被拒绝。当指定时间过去后,将计数重置为0,重新开始计数。
弊端:如果在单位时间1s内只能允许100个请求访问,在前10ms已经通过了100个请求,那后面的990ms所有的请求都会被拒绝,这种现象称为“突刺现象”。
漏桶算法
漏桶算法可以消除"突刺现象",漏桶算法内部有一个容器,类似生活用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃
弊端:无法应对短时间的突发流量。
令牌桶算法
在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
2.3 Gateway 限流支持
在 Spring Cloud Gateway 中,有 Filter 过滤器,因此可以在 pre 类型的 Filter 中自行实现上述三种过滤器。但是限流作为网关最基本的功能,Spring Cloud Gateway 官方就提供了 RequestRateLimiterGatewayFilterFactory 这个类,适用在 Redis 内的通过执行 Lua 脚本实现了令牌桶的方式。具体实现逻辑在 RequestRateLimiterGatewayFilterFactory 类中,lua 脚本在如下图所示的文件夹中:
2.4 实例
- 添加 redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
- 在配置文件中配置
server:
# 配置应用端口
port: 8080
spring:
application:
# 配置应用名称
name: gateway
cloud:
nacos:
discovery:
# 注册服务中心地址
server-addr: 192.168.205.10:8848
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- name: RequestRateLimiter
args:
# 用于限流的键的解析器的 Bean 对象的名字,通过 SpEL 表达式从 Spring 容器中获取
key-resolver: '#{@hostAddrKeyResolver}'
# 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 令牌桶的上限
redis-rate-limiter.burstCapacity: 3
redis:
host: localhost
port: 6379
database: 0
- 自定义限流策略,通过实现 KeyResolver 接口
基于 hostAddress 限流:
public class HostAddrKeyResolver implements KeyResolver {
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
基于 URI 限流:
public class UriKeyResolver implements KeyResolver {
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getURI().getPath());
}
}
基于用户 限流:
public class UserKeyResolver implements KeyResolver {
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}
}
只允许一个策略生效,这里我们采用 HostAddrKeyResolver :
@Bean
public HostAddrKeyResolver hostAddrKeyResolver() {
return new HostAddrKeyResolver();
}
- 启动 OrderService 和 gateway 服务,通过 jmeter 并发访问
可以看到请求部分成功,部分失败。