RateLimiter 的实现思路

  • 最容易想到的方案,每次访问的时候比较时间,超过区间,就重置;没超过就比较count 和limit;
  • 上述方法会出现不均匀问题,造成短时间达到2倍limit。比如1:59 和2:01;
  • 使用 bucketToken的思路,均匀的投放令牌,guva提供了一个非常好的思路,不必使用 timer 真的投放,而是在获取token的时候,查看时间,看已经积攒了多少个token;
  • 分布式的问题,可以使用redis来解决;为了防止race condition,可以使用lua 脚本;
package com;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class RateLimitSimple {
    Map cachedLimitors = new HashMap<>();
    Map cachedBuckets = new HashMap<>();

    private long intervalInMiss = 1000* 60;
    private long capLimit =1000;

    /// 存在的问题:
    /// 20:59.59 访问1000; 20:60:01 访问1000,有不均匀的问题;可能瞬间造成很大的2倍的流量;
    public boolean canAccess(String key){
        long currentMills = System.currentTimeMillis();
        RateLimitor rateLimitor = cachedLimitors.get(key);
        if(Objects.isNull(rateLimitor)){
            cachedLimitors.put(key, new RateLimitor(currentMills,capLimit));
            return true;
        }else{
            long diff = currentMills - rateLimitor.startTimer;
            if(diff > intervalInMiss){
                rateLimitor.startTimer =currentMills;
                rateLimitor.cap = capLimit;
                cachedLimitors.put(key,rateLimitor);
                return true;
            }else{
                if(rateLimitor.cap >capLimit){
                    return false;
                }else{
                    rateLimitor.cap = rateLimitor.cap+1;
                    cachedLimitors.put(key,rateLimitor);
                    return true;
                }
            }

        }
    }

    /// 使用 token bucket,文件问题是如何均匀的往筒里面放入token。guvaa里面有非常经典的方法
    /// 不用timer,而是在取token的时候,按照时间戳,计算一下,过去放入了多少个token;
    public boolean canAccessUsingBucket(String key){
        long currentMills = System.currentTimeMillis();
        BucketToken bucketToken = cachedBuckets.get(key);
        if(Objects.isNull(bucketToken)){
            cachedBuckets.put(key, new BucketToken(currentMills,capLimit));
            return true;
        }else{
            long diff = currentMills - bucketToken.lastFilledTimer;
            long remaining = 0;
            if(diff > intervalInMiss){
                remaining = capLimit;
            }else{
                long grantedToken = diff/intervalInMiss * capLimit;
                remaining  = Math.min(grantedToken+bucketToken.remainingToken, capLimit);
            }
            bucketToken.lastFilledTimer = currentMills;
            if(remaining == 0){
                bucketToken.remainingToken = 0;
                return false;
            }else{
                bucketToken.remainingToken = remaining-1;
                return true;
            }

        }
    }

    public boolean canAccessUsingRedis(String key){
        /// 对于上面两种,都可以使用redis来实现,第一种方法,无法规避不均匀的问题;
        /// 第二种方法,可以把 bucketTOken 存在 redis中;
        /// 为了规避 多线程的问题, 需要使用lua 脚本来规避,这样就比较ok了
        return true;
    }

    class RateLimitor{
        long startTimer;
        long cap;
        public RateLimitor(long startTimer, long cap){
            this.startTimer = startTimer;
            this.cap = cap;
        }
    }

    class BucketToken{
        long lastFilledTimer;
        long remainingToken;

        public BucketToken(long lastFilledTimer, long remainingToken){
            this.lastFilledTimer = lastFilledTimer;
            this.remainingToken = remainingToken;
        }
    }
}


你可能感兴趣的:(RateLimiter 的实现思路)