一、熔斷降級
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 並發訪問

可以看到請求部分成功,部分失敗。
