Redis实现滑动窗口计数
最近在做项目遇到一个需求,系统需要限制同一IP下参加活动的人数,超过限制人数返回错误,也就是所谓的限流,旁边的大佬提出了用滑动窗口计数来解决,记录一下具体代码实现。
实现原理:使用Redis的zset实现,三个参数key、windowInSecond、maxcount分别表示IP,滑动窗口大小和最大限制数。
getCount()方法:获取zset中的元素个数。
increment()方法:首先获取当前时间(毫秒)和窗口的左边界时间(毫秒),执行removeRangeByScore方法(将0~左边界时间间隔内的成员按照score清除),执行add方法(当前时间作为分数添加到value中),执行expire方法(设置key的失效时间)。其中multi和exec方法是添加事务。
getLastTime()方法:获取key的剩余失效时间。
access()方法:程序的入口,获取key的value中的有效访问次数,如果大于maxcount,则限流,否则调用increment方法,将窗口内的访问数加一。
@Component
@Slf4j
public class SlidingWindowCounter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void increment(String key, Integer windowInSecond, Integer maxcount) {
// 当前时间
long currentMs = System.currentTimeMillis();
// 窗口时间
long maxScoreMs = currentMs - windowInSecond * 1000;
try {
redisTemplate.multi();
BoundZSetOperations<String, String> bound = redisTemplate.boundZSetOps(key);
// 按分数清除过期成员
bound.removeRangeByScore(0, maxScoreMs);
// 添加当前时间分数
bound.add(currentMs + "_" + Math.random(), currentMs);
// 设置超时时间
bound.expire(windowInSecond, TimeUnit.SECONDS);
redisTemplate.exec();
} catch (Exception e) {
redisTemplate.discard();
log.error("窗口内容错误:{}", e.getMessage());
public Long getCount(String key) {
try {
BoundZSetOperations<String, String> bound = redisTemplate.boundZSetOps(key);
// 按key统计集合中的有效数量
return bound.zCard();
} catch (Exception e) {
log.error("获取窗口大小错误:{}", e.getMessage());
return 0L;
public Long getLastTime(String key) {
try {
return redisTemplate.getExpire(key);
} catch (Exception e) {
log.error("获取超时时间错误:{}");
return 0L;
public boolean access(String key, Integer maxcount, Integer windowInSecond) {
Long cnt = getCount(key);
if (cnt.compareTo(maxcount.longValue()) < 0) {
increment(key, windowInSecond, maxcount);
log.info("{}:{}:{},result:true", key, cnt, getLastTime(key));
return true;