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

时间:2017-05-18 08:26:16

标签: java rest spring-mvc spring-boot controller

我正在开发一个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;
    }
}

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

感谢任何帮助。

3 个答案:

答案 0 :(得分:11)

您在Spring中没有该组件。

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

答案 1 :(得分:7)

Spring没有开箱即用的速率限制。

有一个bucket4j-spring-boot-starter项目,该项目使用带有令牌桶算法的bucket4j库来限制对REST api的访问速率。您可以通过应用程序属性文件进行配置。 limit the access based on IP address or username有一个选项。

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

bucket4j:
  enabled: true
  filters:
  - cache-name: buckets
    url: .*
    rate-limits:
    - bandwidths:
      - capacity: 5
    time: 10
    unit: seconds

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

答案 2 :(得分:0)

对于那些寻求限制每个用户(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.sendError(429,"Too many requests");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private boolean isMaximumRequestsPerSecondExceeded(String clientIpAddress){
        int requests = 0;
        try {
            requests = requestCountsPerIpAddress.get(clientIpAddress);
            if(requests > MAX_REQUESTS_PER_SECOND)
                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() {

    }
}

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

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

编辑:确保让Spring在保存过滤器的程序包上进行组件扫描,否则过滤器将无法工作。另外,由于该过滤器带有@Component注释,因此默认情况下(/ *),该过滤器将适用于所有端点。

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

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