RedisTemplate 实现通用限流器

问题

一个安全的接口限流肯定少不了,登录尤其如此。比如验证码发送,验证码验证试错,密码登录试错,这些虽然是不同的业务,但是目的都是一个就是,对于某些用户某种行为一段时间内的的允许次数进行限制

对于此我们抽象出来四个东西

  • 用户的身份标识 userId
  • 行为标识 actionKey
  • 时间周期 period
  • 允许最大次数 maxCount

接下来我们借助Redis来实现这功能

Redis中有一种数据类型 zset ,简单来说一种set,值唯一,除此之外还多了一个特性,zset结构还有一个score字段,可以对插入的值根据score进行排序,我们可以以此做文章。

首先可以以时间作为score的值,配合设置的时间周期period 来形成一个滑动窗口,然后统计窗口内的key数量,如果数量超过最大限制次数,则进行限制访问;如果小于最大限制次数,则可以继续访问。

@Component
public class SimpleRateLimiter {
    
    
	@Autowired
	private RedisTemplate redisTemplate;
	
	public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
    
    
	    String key = String.format("hist:%s:%s", userId, actionKey);
	    long nowTs = System.currentTimeMillis();
	    
	    List<Object> result = (List<Object>) redisTemplate.execute(new SessionCallback<List<Object>>() {
    
    
	        @Override
	        public List<Object> execute(RedisOperations ops) throws DataAccessException {
    
    
	            ops.multi();
	            // 记录行为
	            ops.opsForZSet().add(key, "" + nowTs, nowTs);
	            // 移除窗口前的记录
	            ops.opsForZSet().removeRangeByScore(key, 0, nowTs - period * 1000 * 60);
	            // 获取窗口内行为数量
	            ops.opsForZSet().zCard(key);
	            // 设置过期时间等于窗口长度
	            ops.expire(key, period, TimeUnit.MINUTES);
	            return ops.exec();
	        }
	    });
	    // 获取第三步返回的结果
	    Long count = (Long) result.get(2);
	    return count <= maxCount;
	}

}

猜你喜欢

转载自blog.csdn.net/qq_36838406/article/details/108271924