常见限流方法实现

一、概况

限流防刷我认为有两种,一种是限制外层的请求到达服务器的数量,一种是服务器上的业务层限制用户调用接口的次数。

下面我将分别介绍这两者的具体实现。

二、业务上做接口防刷

1.实现原理

借助redis,key为用户id+请求的url,value为请求次数,过期时间根据业务情况设置。有漏洞,详情见下面的计数器算法说明。

2.具体实现

SpringBoot项目中,自定义@AccessLimit(limit = a, timeScope = b)注解,代表在b秒内最多请求a次,在Controller方法上面声明该注解,在写一个拦截器对每个Controller方法进行拦截,取出@AccessLimit中的值(如果有声明的话),从Redis中取当前用户调用当前RequestURI次数,进行判断即可。

具体代码:

AccessLimit:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 基于注解的调用次数限制
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
    /**
     * 请求限制数
     * @return
     */
    int limit();


    /**
     * 时间范围
     * @return
     */
    int timeScope();

}

拦截器 LimitInterceptor:

@Component
public class LimitInterceptor implements HandlerInterceptor {

    @Autowired
    RedisUtil redisUtil;

    private static final String PATH_LIMIT_REACHED = "调用太过频繁,请稍后再试";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            // 拿到注解的内容
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (accessLimit == null) {
                // 不需要限流验证
                return true;
            } else {
                // 需要限流验证
                int limit = accessLimit.limit();
                int timeScope = accessLimit.timeScope();
                // 次数校验(借助redis实现基于用户的限流验证)
                JwtPlayload userInfo = (JwtPlayload) request.getAttribute("userInfo");
                String redisKey = RedisPre.LIMITPRE+userInfo.getUserId()+request.getRequestURI();
                Integer currentCount = (Integer)redisUtil.get(redisKey);
                if (currentCount!=null) {
                    if (currentCount < limit) {
                        redisUtil.incr(redisKey, 1);
                    } else {
                        // 访问过于频繁
                        throw new FlashSaleException(PATH_LIMIT_REACHED);
                    }
                } else {
                    redisUtil.set(redisKey, 1, timeScope);
                }
            }
        }
        return true;
    }

}

配置类 WebConfig:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LimitInterceptor limitInterceptor;

    /**
     * 注册拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(limitInterceptor).addPathPatterns("/**");
    }
}

Controller:

 @AccessLimit(limit = 10, timeScope = 60) // 限制60秒内只能请求10次
public void helloWorld(){
    return "Hello,World";
}

三、使用Nginx做请求流量限制

1.实现原理

计数器:

设置一个计数器counter,其有效时间为1分钟(即每分钟计数器会被重置为0),每当一个请求过来的时候,counter就加1,如果counter的值大于100,就说明请求数过多;

这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题。限制请求数为100,如果当前请求数为1,同时过来200个请求,查询的时候都发现可以请求,然而这一时间段内请求数已经达到200。

令牌桶算法:

令牌桶是一个存放固定容量令牌的桶,按照固定速率r往桶里添加令牌;桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃;

当一个请求达到时,会尝试从桶中获取令牌;如果有,则继续处理请求;如果没有则排队等待或者直接丢弃;

漏桶算法:

如下图所示,有一个固定容量的漏桶,按照常量固定速率流出水滴;如果桶是空的,则不会流出水滴;流入到漏桶的水流速度是随意的;如果流入的水超出了桶的容量,则流入的水会溢出(被丢弃);

可以发现,漏桶算法的流出速率恒定或者为0,而令牌桶算法的流出速率却有可能大于r;

2.具体实现

对Nginx进行配置: eg:

geo  $limited  { # the variable created is $limited
      default          1;
      127.0.0.1/32     0;
    }

map $limited $limit {
    1 $binary_remote_addr;
    0 "";
    }

limit_req_zone $binary_remote_addr zone=req_one:20m rate=12r/s; #对请求速率进行限制
limit_conn_zone $binary_remote_addr zone=addr:10m;
#对每个ip连接数量进行限制
limit_conn_zone $server_name zone=perserver:20m;
#对每个服务的连接数量进行限制
server{

	listen 80;
	
	location / {
		proxy_pass http://13.11.123.31:80;
		limit_req zone=req_one burst=100 nodelay;
		limit_conn addr 15;
	}
	
}

具体配置参考:

https://www.cnblogs.com/biglittleant/p/8979915.html

https://segmentfault.com/a/1190000016509710

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值