赞
踩
在一些业务场景中,我们可能会提供一些API接口来给用户使用,这些接口是不需要认证的。比如:忘记密码,重置密码的接口,这些接口用户可以随意调用。为了防止这些接口被攻击者恶意频繁调用,消耗我们的系统资源,通常我们会对这些接口做限流保护,否则可能会导致我们的服务器的宕机。
其实限流可以认为是服务降级的一种,限流通过限制请求的流量以达到保护系统的目的。今天我们一起来讨论一下在Redis缓存中怎么通过Redisson实现滑动窗口限流。
我们先来了解一下滑动窗口限流的实现原理,话不多说,先上图。
从上图我们可以得知,有一个时间窗口随着时间轴在向右移动,我们可以记录这个时间窗口内的请求次数,当超过我们允许的阈值时,则进行限流不能继续进行请求。
主要实现步骤如下:
所以,我们使用滑动窗口的思想是,只保留当前时间窗口类的请求记录,而丢弃当前窗口之外的记录。当下次有请求进来时,我们只需要判断当前窗口内的请求是否超过阈值就可以了。未超过则放行,超过则限流。
根据以上步骤我们写了一段伪代码,如果考虑并发场景则需要考虑使用Lua脚本。
- public boolean allowRequest(String requestKey) {
- // 定义一个时间窗口为1分钟
- long windowSize = Duration.ofMinutes(1).toSeconds();
-
- // 定义时间窗口内请求阈值为100
- long limit = 100;
-
- // 当前时间戳
- long currentTime = System.currentTimeMillis();
-
- // 窗口开始时间为当前时间戳 - 窗口大小
- long windowStart = currentTime - windowSize * 1000;
-
- // 删除当前窗口开始之前的所有数据
- Jedis.zremrangeByScore(requestKey, "0", String.valueOf(windowStart));
-
- // 计算当前窗口内的请求总数
- long count = Jedis.zcard(requestKey);
-
- if (count < limit) {
- // 如果允许访问,则将当前请求加到窗口内
- Jedis.zadd(requestKey, currentTime, String.valueOf(currentTime));
- return true;
- }
- return false;
- }
- }
在redisson中已经为我们实现好了滑动窗口限流,通过redissonClient拿到限流器后,配置好时间窗口和限流速率就能直接使用了。实现原理和上面我们的伪代码是一样的,只是它将这一部分封装好了,我们拿到后开箱即用。直接上代码:
- import jakarta.servlet.FilterChain;
- import jakarta.servlet.ServletException;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import org.redisson.api.RRateLimiter;
- import org.redisson.api.RateIntervalUnit;
- import org.redisson.api.RateType;
- import org.redisson.api.RedissonClient;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.http.HttpStatus;
- import org.springframework.web.filter.OncePerRequestFilter;
-
- public class RateLimiterFilter extends OncePerRequestFilter {
-
- private final Logger log = LoggerFactory.getLogger(RateLimiterFilter.class);
-
- private static final String RATE_LIMIT_KEY = "rateLimit:yourApiKey";
- private static final int MAX_REQUESTS_PER_MINUTE = 10;
-
- private final RedissonClient redissonClient;
-
- public RateLimiterFilter(RedissonClient redissonClient) {
- this.redissonClient = redissonClient;
- }
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
- RRateLimiter rateLimiter = redissonClient.getRateLimiter(RATE_LIMIT_KEY);
- // rateLimiter.trySetRate就是设置限流参数,RateType有两种,OVERALL是全局限流 ,PER_CLIENT是单Client限流(可以认为就是单机限流),这里我们只讨论全局模式。
- // 而后面三个参数的作用就是设置在多长时间窗口内(rateInterval+IntervalUnit),许可总量不超过多少(rate)
- // 上面代码中我设置的值就是1分钟内总许可数不超过10个
- rateLimiter.trySetRate(RateType.OVERALL, MAX_REQUESTS_PER_MINUTE, 1, RateIntervalUnit.MINUTES);
- // 调用rateLimiter的tryAcquire()或者acquire()方法即可获取许可
- if (!rateLimiter.tryAcquire()) {
-
- String path = request.getRequestURI().substring(request.getContextPath().length());
- log.error("当前请求触发限流策略,请求: {} 已经被限流.", path);
-
- response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
- response.getWriter().write("Too many requests!");
- return;
- }
- filterChain.doFilter(request, response);
- }
- }
滑动窗口限流是一种流量控制策略,用于控制在一定时间内的请求频率。它的主要优点是可以在单位时间内平滑的控制流量,而不是简单的设置固定的请求数或速率。这使得系统可以更灵活的应对突发流量或峰值流量,而不会因为固定速率的限制而浪费或降低系统性能。
这种限流算法可以在分布式系统、API服务等各种场景中使用,以确保系统的稳定性和可用性,防止过多的请求或恶意请求对系统造成负担或崩溃。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。