四时宝库

程序员的知识宝库

在SpringBoot中实现简单限流功能(springboot限流降级)

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。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接