如何使用redis + lua实现限流特技?

如何正确的姿态实现限流特技?

 

为什么需要实现限流?

由于业务应用系统的负载能力有限,为了防止非预期的请求对系统压力过大而拖垮业务应用系统,必须采取流量控制措施。

扩展:如果需求是如何实现控制用户访问次数,比如说限时秒杀,防止同一个用户在指定的时间内操作次数过多,是否可以使用同样的方式呢?

话不多说,直接上代码,结合代码会更加的生动形象。

【代码实现方式: 自定义注解 + 拦截器 + redis结合lua 实现高效限流】

第一步:

 自定义限流的注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtLimit {

    /**限制次数*/
    int limitNum() default 0;
    
    /**过期时间*/
    int expireTime() default 0;
    
}

 

上面两个参数联系起来就是在指定的时间内,能访问的次数是多少,一旦超过次数,不好意思,你需要等等了。

 

第二步:

注解定义好了,我们该如何使用这个注解,我们那就定义一个拦截器吧,检测到是这个注解ExtLimit 的,我们就把你拦下来,验证你一番再说。

public class ExtAnnotationInterceptor extends BaseResponse implements HandlerInterceptor{

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    
    @Autowired
    ConfigurationUtil configurationUtil;
    
    @Autowired
    ErrorCodeMsgUtil errorCodeMsgUtil;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 判断接口是否有注解ExtLimit
        ExtLimit methodAnnotation = method.getAnnotation(ExtLimit.class);
        extLimitAnnotationHandler(response, method, methodAnnotation);
        return true;
    }
    
    //注解@ExtLimit处理
    public boolean extLimitAnnotationHandler(HttpServletResponse response,Method method,ExtLimit methodAnnotation) throws Exception{
        if(methodAnnotation == null) {
            return true;
        }
        //2. 获取限流参数
        int limitNum = methodAnnotation.limitNum();
        int expireTime = methodAnnotation.expireTime();
        if(limitNum == 0 || expireTime == 0){
            return true;
        }
        //3. 调用redis lua脚本实现限流
        String executeStr = stringRedisTemplate.execute(RedisLuaScriptUtil.LIMIT_REDIS_SCRIPT,  Collections.singletonList(configurationUtil.getServerName()+":"+method.getName()),String.valueOf(limitNum),String.valueOf(expireTime));
        //4.在指定的时间内超过限制次数返回结果
        if(Constants.FALSE.equals(executeStr)){

            //表示直接响应一个json格式的信息
            ResponseUtil.sendJsonMessage(response, setResultFail(errorCodeMsgUtil.getLimitErrorCode(),errorCodeMsgUtil.getLimitErrorMsg()));
            return false;
        }
        return true;
    }
}

注释【3. 调用redis lua脚本实现限流】,是redis结合lua实现的一个在指定时间内的计数RedisLuaScriptUtil是个工具类,如下:

public class RedisLuaScriptUtil {

    
    /**对接口做限流操作,采用redis+lua,时间单位:s*/
    public static final RedisScript<String> LIMIT_REDIS_SCRIPT = new DefaultRedisScript<String>(
            "local key = KEYS[1] "
          + "local limit = ARGV[1] "
          + "local expireTime = ARGV[2] "
          + "if redis.call('EXISTS',key) == 1 then "
              + "if tonumber(redis.call('GET',key)) >= tonumber(limit)  then "
                      + "return 'false'"
              + "else "
                      + "redis.call('INCRBY',key,'1') "
                      + "return 'true'"
              + "end "
          +"else "
              + "redis.call('SET',key,'1','EX',expireTime) "
              + "return 'true'"
          +"end "
        ,String.class);
    
    
    /**对接口幂等性做限制,由单独的token服务生成是分布式全局id*/
    public static final RedisScript<String> IDEMPOTENCY_REDIS_SCRIPT = new DefaultRedisScript<String>(
            "local IdempotencyToken = KEYS[1] "
          + "if redis.call('EXISTS',IdempotencyToken) == 1 then "
                + "redis.call('DEL',IdempotencyToken) "
                + "return 'true' "
          + "else "
                + "return 'false'"
          + "end ",
            String.class);
    
    
    /**分布式加锁*/
    public static final RedisScript<String> DISTRIBUTED_LOCK_SCRIPT = new DefaultRedisScript<String>(
            "local distributedKey = KEYS[1] "
          + "local distributedValue = ARGV[1] "
          + "local expireTime = ARGV[2] "
          + "if redis.call('SETNX',distributedKey,distributedValue) == 1 then "
                + "redis.call('EXPIRE',distributedKey,expireTime) "
                + "return 'true'"
          + "else "
                + "return 'false' "
          + "end",
            String.class);
    
    /**分布式释放锁*/
    public static final RedisScript<String> DISTRIBUTED_UNLOCK_SCRIPT = new DefaultRedisScript<String>(
            "local distributedKey = KEYS[1] "
          + "local distributedValue = ARGV[1] "
          + "if redis.call('GET',distributedKey) == distributedValue then "
                + "redis.call('DEL',distributedKey) "
                + "return 'true'"
          + "else "
                + "return 'false'"
          + "end ", 
            String.class);
}

 

以上,是整个实现限流的流程,接下来简单的说下扩展:

扩展:如果需求是如何实现控制用户访问次数,比如说限时秒杀,防止同一个用户在指定的实现操作次数过多,是否可以使用同样的方式呢?

如果防止客户端频繁的操作,我们仅仅只需要将上面一段代码中的Collections.singletonList(configurationUtil.getServerName()+":"+method.getName())

        String executeStr = stringRedisTemplate.execute(RedisLuaScriptUtil.LIMIT_REDIS_SCRIPT,  Collections.singletonList(configurationUtil.getServerName()+":"+method.getName()),String.valueOf(limitNum),String.valueOf(expireTime));

只需要method.getName() 换成客户端ip即可,这样是不是简单的控制了一个ip来源的访问次数,其实还有很多的解决方案,大家自行扩展即可。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值