令牌桶简单实现(Java)

令牌桶简单实现(Java)

简介

令牌桶
百度可得,令牌桶是一个桶,定时往里面放令牌,然后请求来了从令牌桶里取令牌,取到了继续后续逻辑,没取到就拦截不让请求,达到限流目的

实现思路

参考了guava的RateLimiter之后,发现并没有“一个线程定时往桶里放token”,而是在请求进来时通过当前时间戳实时计算(下一次加令牌的时间为x,此时时间为m,平均n秒放一个令牌,则此时应当增加 (m - x) / n + 1 个令牌)

code

/**
 * 令牌桶
 * 局限:最多1毫秒生成一个令牌
 *
 * create time: 2020/7/15 9:42
 */
public class TokenBucket {

    private final double unitAddNum;    // 单位时间(1s)往桶中放令牌数量
    private final long maxTokenNum;      // 桶中最大有多少令牌

    private volatile long currentTokenCount = 0;  // 当前桶中有多少令牌
    private volatile long nextRefreshTime = 0L;  // 下一次刷新桶中令牌数量的时间戳
    private volatile long lastAcquireTime;       // 上一次从桶中获取令牌的时间戳(貌似用不到)

    /**
     *
     * @param unitAddNum 1秒增加几个令牌
     * @param maxToken 桶中最大令牌数
     * @param isFullStart 一开始是否是满的
     */
    public TokenBucket(double unitAddNum, long maxToken, boolean isFullStart) {
        if (unitAddNum <= 0 || maxToken <= 0) {
            throw new RuntimeException("unitAddNum and maxToken can't less than 0");
        }
        if (unitAddNum > 1000) {
            throw new RuntimeException("unitAddNum max is 1000");
        }
        this.unitAddNum = unitAddNum;
        this.maxTokenNum = maxToken;
        if (isFullStart) {
            this.currentTokenCount = maxToken;
        } else {
            this.currentTokenCount = 0;
        }
        this.nextRefreshTime = calculateNextRefreshTime(System.currentTimeMillis());
    }

    public boolean acquire(long needTokenNum) {
        if (needTokenNum > this.maxTokenNum) {
            return false;
        }
        synchronized (this) {
            long currentTimestamp = System.currentTimeMillis();
            this.refreshCurrentTokenCount(currentTimestamp);
            if (needTokenNum <= this.currentTokenCount) {
                return this.doAquire(needTokenNum, currentTimestamp);
            }
            return false;
        }
    }

    private boolean doAquire(long needTokenNum, long currentTimestamp) {
        this.currentTokenCount -= needTokenNum;
        this.lastAcquireTime = currentTimestamp;
        return true;
    }

    /**
     * 刷新桶中令牌数量
     * @param currentTimestamp
     */
    private void refreshCurrentTokenCount(long currentTimestamp) {
        if (this.nextRefreshTime > currentTimestamp) {
            return;
        }
        this.currentTokenCount = Math.min(this.maxTokenNum, this.currentTokenCount + calculateNeedAddTokenNum(currentTimestamp));
        this.nextRefreshTime = calculateNextRefreshTime(currentTimestamp);

    }

    /**
     * 计算当前需要添加多少令牌
     * @param currentTimestamp
     * @return
     */
    private long calculateNeedAddTokenNum(long currentTimestamp) {
        if (this.nextRefreshTime > currentTimestamp) {
            return 0;
        }
        long addOneMs = Math.round(1.0D / this.unitAddNum * 1000D); // 这么久才能加1个令牌
        return (currentTimestamp - this.nextRefreshTime) / addOneMs + 1;
    }

    private long calculateNextRefreshTime(long currentTimestamp) {
        if (currentTimestamp < this.nextRefreshTime) {
            return this.nextRefreshTime;
        }
        long addOneMs = Math.round(1.0D / this.unitAddNum * 1000D); // 这么久才能加1个令牌
        long result = 0;
        if (this.nextRefreshTime <= 0) {
            result = currentTimestamp + addOneMs;
        } else {
            result = this.nextRefreshTime + ((currentTimestamp - this.nextRefreshTime) / addOneMs + 1) * addOneMs;
        }
        return result;
    }

    public static void main(String[] args) throws InterruptedException {
        TokenBucket tokenBucket = new TokenBucket(1D, 1, true);
        for (int i=0; i<10; i++) {
            System.out.println(tokenBucket.acquire(1));
            Thread.sleep(500);
        }
    }
}

main输出结果

true
false
true
false
true
false
true
false
true
false
令牌桶算法是一种流量控制算法,可以用于限制某个接口或服务的访问频率。下面是一种简单Java实现: ```java import java.util.concurrent.atomic.AtomicLong; public class TokenBucket { private final AtomicLong tokens; // 当前可用的令牌数 private final int capacity; // 令牌桶的容量 private final long refillInterval; // 令牌填充的时间间隔 private final long refillAmount; // 每次填充的令牌数 private volatile long lastRefillTime; // 上次填充令牌的时间 public TokenBucket(int capacity, long refillInterval, long refillAmount) { tokens = new AtomicLong(capacity); this.capacity = capacity; this.refillInterval = refillInterval; this.refillAmount = refillAmount; lastRefillTime = System.currentTimeMillis(); } // 尝试获取令牌,成功返回true,失败返回false public boolean tryAcquire() { refill(); return tokens.getAndUpdate(n -> n > 0 ? n - 1 : 0) > 0; } // 填充令牌 private void refill() { long now = System.currentTimeMillis(); long elapsedTime = now - lastRefillTime; if (elapsedTime > refillInterval) { long newTokens = elapsedTime / refillInterval * refillAmount; lastRefillTime += elapsedTime / refillInterval * refillInterval; tokens.updateAndGet(n -> Math.min(capacity, n + newTokens)); } } } ``` 在上述实现中,TokenBucket类的构造函数需要传入令牌桶的容量、令牌填充的时间间隔以及每次填充的令牌数。tryAcquire方法用于尝试获取一个令牌,如果当前可用令牌数大于0,则将可用令牌数减1并返回true,否则直接返回false。refill方法用于填充令牌,如果当前时间距离上次填充令牌的时间超过了填充间隔,则计算应该填充的令牌数,并更新可用令牌数。注意,在更新可用令牌数时,需要使用AtomicLong类来保证线程安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值