当前位置:   article > 正文

限流——令牌桶算法_令牌桶算法计算限流

令牌桶算法计算限流

限流——令牌桶算法

令牌桶简单来说就是有一个桶,然后假设里面存放了1000个令牌,我们访问一个接口需要有一个令牌,然后令牌桶中就会减少1个令牌,所以最多只能有1000个请求拿到令牌,但是,我们一般不会只有1000个令牌,所以,我们还需要设置每隔一段时间就会自动生成令牌

然后我们使用lua+redis来进行令牌桶限流代码的编写
这里使用的是springboot+gateway,先上代码

TokenBucket.class


@Slf4j
public class TokenBucket {


    //当前令牌桶的key
    private String key;

    //令牌桶的最大容量
    private int maxTokens;

    //每秒产生令牌的数量
    private int secTokens;

    private StringRedisTemplate redisTemplate;

    public TokenBucket(String key, int maxTokens, int secTokens){
        this.key=key;
        this.maxTokens=maxTokens;
        this.secTokens=secTokens;
        //手动从容器中获取Redis模板对象
        this.redisTemplate= SpringUtils.getBean(StringRedisTemplate.class);
        init();
    }

    //初始化令牌桶
    private void init(){
        log.info("###################开始进行令牌桶初始化#######################");
        //初始化token lua脚本
        Properties info = redisTemplate.getConnectionFactory().getConnection().info();
        String server = info.getProperty("server");
        //执行lua脚本
        redisTemplate.execute(new DefaultRedisScript(TokenLua.initBucket),
                Collections.singletonList(String.valueOf(this.key)),
                String.valueOf(this.maxTokens),
                String.valueOf(this.secTokens),
                TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis())+"");
		
			log.info("###################令牌桶初始完成#######################");
    }


    /**
     * 传入领取的令牌数,返回需要等待的时间
     */
    public double getTokens(int tokens)  {
        long waitTime= (long) redisTemplate.execute(new DefaultRedisScript<Long>(TokenLua.getToken,Long.class),
                Collections.singletonList(String.valueOf(key)),
                String.valueOf(tokens+""));

        if (waitTime>0){
            try {
                Thread.sleep(waitTime/1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return waitTime;
    }


    /**
     * 传入领取的令牌数,加上一个超时时间,如果在超时时间之内
     * 没办法领取令牌,就直接返回失败
     */
    public boolean getTokens(int tokens,int timout,TimeUnit unit) {
        long waitTime= (long) redisTemplate.execute(new DefaultRedisScript<Long>(TokenLua.getToken,Long.class),
                Collections.singletonList(String.valueOf(key+"")),
                String.valueOf(tokens+""),
                unit.toMicros(timout)+"");

        if (waitTime==-1){
            return false;
        }
        if (waitTime>0){
            try {
                Thread.sleep(waitTime/1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
    /**
     * 传入领取的令牌数,如果能立即领取,就返回true,就直接返回false
     */
    public boolean getTokensNow(int tokens) {
        return getTokens(tokens,0,TimeUnit.MICROSECONDS);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

SpringUtils.class

//用来从IOC容器获取一个Bean
@Component
public class SpringUtils implements BeanFactoryAware {

    private static BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory=beanFactory;
    }


    public static <T> T getBean(Class c){
        return (T) beanFactory.getBean(c);
    }


    public static <T> T getBean(String beanName){
        return (T) beanFactory.getBean(beanName);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

使用方法,在这里以我们的gateway网关为例


@Component
public class TokenLimitFilter implements GatewayFilter {

    /**
     * key  --  当前请求的url
     * value  --  当前url对应的令牌桶
     */
    private Map<String, TokenBucket> tongMap=new ConcurrentHashMap();

    @Autowired
    private BucketConfig bucketConfig;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //令牌桶限流---URL限流
        ServerHttpRequest request = exchange.getRequest();
        String requestPath = request.getPath().value();

        Map<String, BucketConfig.Bucket> map = bucketConfig.getMap();
        BucketConfig.Bucket bucket = map.get(requestPath);

        TokenBucket tokenBucket = tongMap.computeIfAbsent(requestPath,
                s->  new TokenBucket(requestPath,bucket.getMaxToken(),bucket.getSecToken()));

        boolean flag = tokenBucket.getTokensNow(2);

        if (flag){
            System.out.println("拿到令牌");
            //请求放行
            return chain.filter(exchange);
        }
        //没有拿到令牌,直接返回服务器繁忙
        Result resultData = Result.fail(ResultCode.SERVER_BUSY);

        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().put("Content-Type", Collections.singletonList("application/json?charset=utf-8"));
        response.getHeaders().put("Access-Control-Allow-Orgin", Collections.singletonList("*"));
        DataBuffer dataBuffer = null;
        try {
            dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(resultData).getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        Mono<Void> mono = response.writeWith(Mono.just(dataBuffer));
        System.out.println("没有拿到令牌");
        return mono;

    }

    public Map<String, TokenBucket> getTongMap() {
        return tongMap;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

之后只要在执行请求的时候,接入这个拦截器就行了

@Component
public class TokenLimitFilterFactory extends AbstractGatewayFilterFactory {

    @Autowired
    private TokenLimitFilter tokenLimitFilter;

    @Override
    public GatewayFilter apply(Object config) {
        return tokenLimitFilter;
    }

    @Override
    public String name() {
        return "Token_Limiter";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

配置

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: test_route
          uri: http://www.baidu.com
          predicates:
            - Query=url,test
        - id: product_route
          uri: lb://mall-product
          predicates:
            - Path=/product/**
          filters:
            - RewritePath=/product/(?<segment>.*),/$\{segment}
            - Token_Limiter
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/645679
推荐阅读
相关标签
  

闽ICP备14008679号