如何在Spring Boot中为每个用户设置速率限制?

Ric*_*cky 18 java rest controller spring-mvc spring-boot

我正在开发一个Spring Boot Rest API来处理大量的传入请求调用.我的控制器如下所示:

@RestController

public class ApiController {
    List<ApiObject>  apiDataList;   

    @RequestMapping(value="/data",produces={MediaType.APPLICATION_JSON_VALUE},method=RequestMethod.GET)
    public ResponseEntity<List<ApiObject>> getData(){                                       
        List<ApiObject> apiDataList=getApiData();
        return new ResponseEntity<List<ApiObject>>(apiDataList,HttpStatus.OK);
    }
    @ResponseBody 
    @Async  
    public List<ApiObject>  getApiData(){
        List<ApiObject>  apiDataList3=new List<ApiObject> ();
        //do the processing
        return apiDataList3;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在我想为每个用户设置一个ratelimit.假设每个用户每分钟只能请求5个请求或类似的东西.如何设置每个用户的速率限制,每分钟只能进行5次api呼叫,如果用户请求的次数超过了我可以发回429响应?我们需要他们的IP地址吗?

任何帮助表示赞赏.

Mau*_*ice 21

这是针对那些寻求限制每个用户(IP 地址)每秒请求数的人的解决方案。此解决方案需要 Google 的Guava library. 您将使用LoadingCache该类来存储请求计数和客户端 IP 地址。您还将需要javax.servlet-api依赖项,因为您将希望使用发生servlet filter请求计数的地方。代码如下:

import javax.servlet.Filter;


@Component
public class requestThrottleFilter implements Filter {

    private int MAX_REQUESTS_PER_SECOND = 5; //or whatever you want it to be

    private LoadingCache<String, Integer> requestCountsPerIpAddress;

    public requestThrottleFilter(){
        super();
        requestCountsPerIpAddress = CacheBuilder.newBuilder().
                expireAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<String, Integer>() {
            public Integer load(String key) {
                return 0;
            }
        });
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        String clientIpAddress = getClientIP((HttpServletRequest) servletRequest);
        if(isMaximumRequestsPerSecondExceeded(clientIpAddress)){
          httpServletResponse.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
          httpServletResponse.getWriter().write("Too many requests");
          return;
         }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    private boolean isMaximumRequestsPerSecondExceeded(String clientIpAddress){
        int requests = 0;
        try {
            requests = requestCountsPerIpAddress.get(clientIpAddress);
            if(requests > MAX_REQUESTS_PER_SECOND){
                requestCountsPerIpAddress.put(clientIpAddress, requests);
                return true;
             }
        } catch (ExecutionException e) {
            requests = 0;
        }
        requests++;
        requestCountsPerIpAddress.put(clientIpAddress, requests);
        return false;
    }

    public String getClientIP(HttpServletRequest request) {
        String xfHeader = request.getHeader("X-Forwarded-For");
        if (xfHeader == null){
            return request.getRemoteAddr();
        }
        return xfHeader.split(",")[0]; // voor als ie achter een proxy zit
    }

    @Override
    public void destroy() {

    }
}
Run Code Online (Sandbox Code Playgroud)

所以这基本上做的是它将所有请求制作 ip 地址存储在一个LoadingCache. 这就像一个特殊的映射,其中每个条目都有一个过期时间。在构造函数中,过期时间设置为 1 秒。这意味着在第一次请求时,一个 IP 地址加上它的请求计数只会在 LoadingCache 中存储一秒钟。它会在到期时自动从地图中删除。如果在那一秒内有更多请求来自 IP 地址,isMaximumRequestsPerSecondExceeded(String clientIpAddress)则将这些请求添加到总请求计数中,但在此之前检查是否已超过每秒最大请求量。如果是这种情况,它返回 true 并且过滤器返回一个错误响应,状态码为 429,代表请求过多。

这样,每个用户每秒只能发出一定数量的请求。

编辑:确保让 Spring 对保存过滤器的包进行组件扫描,否则过滤器将无法工作。此外,因为它是用@Component 注释的,所以过滤器默认适用于所有端点(/*)。

如果 spring 检测到您的过滤器,您应该在启动期间在日志中看到类似的内容。

o.s.b.w.servlet.FilterRegistrationBean : Mapping filter:'requestThrottleFilter' to: [/*]

  • 请注意这个答案:它不使用漏桶算法。这意味着,在每秒 5 个请求的设置中,每 0.5 秒完成的 6 个连续请求中的最后一个请求将被阻止。因此,这实际上并不意味着每秒 5 个请求,而是最多 5 个请求,其中每个请求没有至少 1 秒的超时时间。非常不一样。 (4认同)
  • 简单而出色的解决方案。感谢您拯救了这一天。 (3认同)
  • 为什么 Spring Boot 不包含这个很棒的答案呢? (2认同)

Dan*_*edo 12

你在Spring中没有那个组件.

  • 您可以将其构建为解决方案的一部分.创建一个过滤器并在spring上下文中注册它.过滤器应检查传入呼叫并在时间窗口内计算每个用户的传入请求.我会使用令牌桶算法,因为它是最灵活的.
  • 您可以构建一些独立于当前解决方案的组件.创建一个完成工作的API网关.您可以扩展Zuul网关,并再次使用令牌桶算法
  • 您可以使用已经内置的组件,例如Mulesoft ESB,它可以充当API网关并支持速率限制和限制.从来没有用过它
  • 最后,您可以使用具有速率限制和限制功能的API管理器等等.结帐MuleSoft,WSO2,3Scale,Kong等...(大多数都有成本,有些是开源的,有社区版


Luk*_* R. 7

Spring没有开箱即用的限速功能。

有一个bucket4j-spring-boot-starter项目,该项目使用带有令牌桶算法的bucket4j库来限制对REST api的访问。您可以通过应用程序属性文件进行配置。有一个选项可以限制访问基于IP地址或用户名

作为示例,简单设置可以在10秒内独立于用户最多允许5个请求:

bucket4j:
  enabled: true
  filters:
  - cache-name: buckets
    url: .*
    rate-limits:
    - bandwidths:
      - capacity: 5
    time: 10
    unit: seconds
Run Code Online (Sandbox Code Playgroud)

如果您使用的是Netflix Zuul,则可以使用Spring Cloud Zuul RateLimit,它使用不同的存储选项:Consul,Redis,Spring Data和Bucket4j。