Resilience4j它是一个轻量、易用、可组装的高可用框架,支持熔断、高频控制、隔离、限流、限时、重试等多种高可用机制。
在SpringBoot中需要简单的注解即可实现。
下面举个例子演示一下:
在pom中引入依赖如下依赖
<!--
Release 2.x will be using Java 11
Release 1.x is still using Java 8
-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>
application.yml中配置
# ratelimmiter config
management:
endpoints:
web:
exposure:
include: "*"
health:
ratelimiters:
enabled: true
新建配置类RateLimmiterConfig.java。在这个类中我们可以设置
- limitForPeriod
指定特定时间段内允许通过的请求数量。例如,将其设置为10,则表示该时间段内允许10个请求。
- Limmit refresh period
刷新的周期,用来设置重置速率限制的频率。例如,如果将其设置为10s,则表示10s后重置请求数量。
- Time out duration
请求的超时持续时间。如果请求超过此持续时间,则会触发异常。例如,设置3s表示3s内如果没有机会执行,请求将发生失败。
- Rate limmiter registry name
限制器名称。可以是全局的,多个端点一个名称或者一个端点一个名称。
package com.deni.promotion.ratelimmiter;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@Configuration
public class RateLimmiterConfig {
@Autowired
private RateLimiterRegistry rateLimiterRegistry;
@Bean
public RateLimiter globalRateLimiter() {
RateLimiterConfig customConfig = RateLimiterConfig.custom()
.limitForPeriod(10)
.limitRefreshPeriod(Duration.of(1, ChronoUnit.SECONDS))
.timeoutDuration(Duration.of(1, ChronoUnit.SECONDS))
.build();
return rateLimiterRegistry.rateLimiter("globalRateLimiter", customConfig);
}
}
package com.deni.promotion.ratelimmiter;
import com.deni.common.model.response.Response;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import static com.deni.common.model.response.rest.ResponseHelper.error;
@Slf4j
@RestController
@RequestMapping("/ratelimmiter")
public class RateLimmiterTestController {
final String url = "http://localhost:9898/promotion/ratelilmmiter/test";
@RequestMapping(value = "/test", method = {RequestMethod.GET})
@RateLimiter(name = "globalRateLimiter", fallbackMethod = "fallbackTest")
public ResponseEntity<Response> test() {
Response response = error(String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.getReasonPhrase());
return ResponseEntity.status(HttpStatus.OK).body(response);
}
public ResponseEntity<Response> fallbackTest(RequestNotPermitted exception) {
Response response = error(String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()), "Too many request, please try again!");
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(response);
}
}
测试:
package com.deni.promotion.controller;
import com.deni.promotion.gt.GTTestApp;
import com.deni.promotion.config.DataSourceTestConfig;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {
GTTestApp.class,
DataSourceTestConfig.class
}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@Slf4j
public class RateLimmiterControllerTest {
private RestTemplate restTemplate = new TestRestTemplate().getRestTemplate();
/**
* max number of request = 1
* times periode in second = 1
* given number of request = 1
* expected = ok
*/
@Test
public void TEST_REQUEST_OK() {
String uri = "http://localhost:9898/promotion/ratelimmiter/test";
RequestEntity<Void> request = RequestEntity
.get(URI.create(uri))
.accept(MediaType.APPLICATION_JSON).build();
ResponseEntity<String> response = restTemplate.exchange(request, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
}
/**
* max number of request = 10
* times periode in second = 1
* given number of request = 100
* expected = too many request
*/
@Test
public void TEST_TOO_MANY_REQUEST() {
Map<Integer, Integer> responseCount = new HashMap<>();
/**
.rangeClosed(startInclusive, endInclusive)
*/
IntStream.rangeClosed(1, 100)
.parallel()
.forEach(i -> {
String uri = "http://localhost:9898/promotion/ratelimmiter/test";
RequestEntity<Void> request = RequestEntity
.get(URI.create(uri))
.accept(MediaType.APPLICATION_JSON).build();
ResponseEntity<String> response = restTemplate.exchange(request, String.class);
// masukan : http status, counter
responseCount.put(response.getStatusCodeValue(), i + 1);
log.info(uri + " | request " + i + " | " + new Date().toString() + " | " + response.getStatusCodeValue());
});
// chek response had too many request (got rate limmiter)
boolean expected = responseCount.containsKey(HttpStatus.TOO_MANY_REQUESTS.value());
Assert.assertTrue(expected);
}
}
测试结果:
在10:40:25的时候
- 每 1 秒收到 16 个请求。
- 其中10 个请求返回200。
- 其中6 个请求返回429。