一:单机式令牌桶的缺陷
在上述的记账中我们讨论使用了Bucket来做限流机制。
/**
* 创建一个令牌桶
* @return
*/
private Bucket createNewBucket() {
Refill refill = Refill.of(1, Duration.ofSeconds(1000));// 1:每次补充令牌的数量 ; 2 补充token的时间间隔
Bandwidth limit = Bandwidth.classic(10, refill); //10,通最多能装10个令牌
return Bucket4j.builder().addLimit(limit).build();
}
我们来进一步查看其中的代码实现
public LocalBucket build() {
BucketConfiguration configuration = buildConfiguration();
switch (synchronizationStrategy) {
case LOCK_FREE: return new LockFreeBucket(configuration, timeMeter);
case SYNCHRONIZED: return new SynchronizedBucket(configuration, timeMeter);
case NONE: return new SynchronizedBucket(configuration, timeMeter, FakeLock.INSTANCE);
default: throw new IllegalStateException();
}
}
这是bucket4j最后使用Build()方法实现的本地的localBucket。事实上,只能在本机上实现,一旦分布则数据不同意。
二:分布式限领令牌桶的可行性讨论
我们还是来说一下限流的机制: 当访问开始消耗桶中的令牌达到最大值,则桶为空报错,期间桶中令牌还按照一定的时间规律
补充。
那么,令牌桶的核心就是3个概念:桶中最大的令牌数,桶补充令牌的时间间隔,桶每次补充令牌的个数。
我们来思考:
(1)令牌桶核心 业务就是:消耗令牌数和新增令牌数。如果我们使用定时任务来添加令牌。比如根据ip限流。
其中实线为定时添加令牌,虚线为访问消耗的令牌。但是这样做的消费太高,一个ip就要创建一个定时任务。
(2)简化版令牌桶:如每10s只允许一个ip访问20次。
实现思路:使用redis的失效时间。即:a拿着ip去访问redis中该ip对应的次数,当次数不为0,则可以访问。当该ip的缓存
达到失效时间,该ip去redis中发现为null,则重新初始化redis。
这样操作,我们不需要管,每一次补充多少,即在一定时间间隔内,访问次数是固定的。缺点没有动态补充。
(3)令牌桶的实现:此方法思路是在(2)的基础上实现,只不过添加了动态补充。