使用redis在SpringCloud getway中進行速率限制
1.依存關係
我們將針對較高流量下的速率限制測試示例應用程式。首先,我們需要包括一些依賴項。當然,需要Spring Cloud Gateway啟動器。為了使用Redis處理速率限制器,我們還需要向spring-boot-starter-data-redis-reactive啟動器添加依賴項。其他依賴關係用於測試目的。mockserver測試容器內提供的模塊。它負責模擬目標服務。反過來,該庫在測試期間mockserver-client-java用於與mockserver容器集成。最後一個庫junit-benchmarks用於基準測試方法並同時運行測試。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mockserver</artifactId>
<version>1.12.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<version>3.10.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.carrotsearch</groupId>
<artifactId>junit-benchmarks</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
該示例應用程式基於Spring Boot構建,2.2.1.RELEASE並使用Spring Cloud Hoxton.RC2Release Train。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version>
<spring-cloud.version>Hoxton.RC2</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.實施
請求速率限制是通過使用稱為的Spring Cloud Gateway組件實現的GatewayFilter。該過濾器的每個實例都由一個特定的工廠構造。過濾器當然負責在發送下遊請求之前或之後修改請求和響應。當前,有30個可用的內置 網關過濾器工廠。
在GatewayFilter有一個可選的keyResolver參數和參數特定於速率限制器的實現(在這種情況下使用的Redis的實施方案)。
參數keyResolver是實現KeyResolver接口的bean 。它允許您應用不同的策略來導出限制請求的密鑰。參數keyResolver是一個實現KeyResolver接口。
它允許您應用不同的策略來導出限制請求的密鑰。遵循Spring Cloud Gateway文檔:的默認實現KeyResolverPrincipalNameKeyResolver它Principal從ServerWebExchange和調用檢索Principal.getName()。默認情況下,如果KeyResolver找不到密鑰,則請求將被拒絕。可以使用spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key(true或false)和spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code屬性來調整此行為。
由於我們已經討論了速率限制的一些理論方面,因此我們可以繼續進行實施。首先,讓我們定義主類和非常簡單的KeyResolverbean,它始終等於一個。
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just("1");
}
}
假設我們具有以下配置,並且目標應用程式在埠上運行,8091我們可以執行一些測試調用。您可以設置兩個屬性以自定義過程。該redis-rate-limiter.replenishRate決定每秒用戶多少個請求被允許發送,沒有任何下降的請求。這是令牌桶被填充的速率。第二個屬性redis-rate-limiter.burstCapacity是允許用戶在一秒鐘內執行的最大請求數。這是令牌桶可以容納的令牌數。將此值設置為零將阻止所有請求。
server:
port: ${PORT:8085}
spring:
application:
name: gateway-service
redis:
host: 192.168.99.100
port: 6379
cloud:
gateway:
routes:
- id: account-service
uri: http://localhost:8091
predicates:
- Path=/account/**
filters:
- RewritePath=/account/(?.*), /$\{path}
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
現在,如果您呼叫網關公開的端點,則會收到以下響應。它包括一些特定的標頭,其前綴為x-ratelimit。標頭x-ratelimit-burst-capacity指示burstCapacity值,x-ratelimit-replenish-rate指示replenishRate值,最重要,指示x-ratelimit-remaining您可能在下一秒內發送的請求數。
如果您超出了允許的請求數量,Spring Cloud Gateway將返回帶有code的響應HTTP 429 - Too Many Requests,並且不會處理傳入的請求。
3.測試
我們有一個Spring Boot測試,該測試使用了Testcontainers提供的兩個Docker容器: MockServer和Redis。因為暴露埠是動態生成的,所以我們需要@BeforeClass在運行測試之前在方法中設置網關屬性。在init方法內部,我們還用於MockServerClient在模擬伺服器容器上定義模擬服務。我們的測試方法在六個線程中同時運行,並重複了600次。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@RunWith(SpringRunner.class)
public class GatewayRateLimiterTest {
private static final Logger LOGGER = LoggerFactory.getLogger(GatewayRateLimiterTest.class);
@Rule
public TestRule benchmarkRun = new BenchmarkRule();
@ClassRule
public static MockServerContainer mockServer = new MockServerContainer();
@ClassRule
public static GenericContainer redis = new GenericContainer("redis:5.0.6").withExposedPorts(6379);
@Autowired
TestRestTemplate template;
@BeforeClass
public static void init() {
System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");
System.setProperty("spring.cloud.gateway.routes[0].uri", "http://192.168.99.100:" + mockServer.getServerPort());
System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");
System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\\{path}");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "RequestRateLimiter");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.replenishRate", "10");
System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.burstCapacity", "20");
System.setProperty("spring.redis.host", "192.168.99.100");
System.setProperty("spring.redis.port", "" + redis.getMappedPort(6379));
new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())
.when(HttpRequest.request()
.withPath("/1"))
.respond(response()
.withBody("{\"id\":1,\"number\":\"1234567890\"}")
.withHeader("Content-Type", "application/json"));
}
@Test
@BenchmarkOptions(warmupRounds = 0, concurrency = 6, benchmarkRounds = 600)
public void testAccountService() {
ResponseEntity<Account> r = template.exchange("/account/{id}", HttpMethod.GET, null, Account.class, 1);
LOGGER.info("Received: status->{}, payload->{}, remaining->{}", r.getStatusCodeValue(), r.getBody(), r.getHeaders().get("X-RateLimit-Remaining"));
}
}
讓我們看一下測試結果。啟動網關後,用戶可以在一秒鐘內發送最多20個請求。超過此值後,它將開始返回HTTP 429。
刪除某些傳入請求後,網關將在下一秒開始接受它們。但是這一次它只允許處理10個請求,這等於replenishRate參數值。