接口限流算法:令牌桶
本文内容:
- 令牌桶算法原理
- 实现令牌桶算法
- Guava中
RateLimiter
令牌桶的使用- 限流算法的应用场景
令牌桶算法原理
简单说明:设定固定的速率往桶中放入令牌,如果到达桶的最大容量就溢出(不能放置)。当每一次接口请求时,需要申请一个令牌,如果获取到则进行业务操作,如果桶中无令牌,则拒绝请求。通过令牌桶就可以对接口进行限流了。
JAVA实现令牌桶算法
package com.dsdj.limiting;
/**
* TokenBucket
* 实现令牌桶算法
**/
public class TokenBucket {
/**
* 桶的容量
*/
private int bucketNums = 100;
/**
* 令牌生成的速率
*/
private int rate =10;
private int nowTokensNums;
private long timestamp = getNowTime();
private long getNowTime(){
return System.currentTimeMillis();
}
private int min(int nowTokensNums){
if(bucketNums > nowTokensNums){
return nowTokensNums;
}else{
return bucketNums;
}
}
public boolean getToken(){
// 按照一定的速率生成令牌
// 当前的时间
long nowTime = getNowTime();
// 当前桶的容量 = 桶容量+时间*速率
nowTokensNums = nowTokensNums+(int) ((nowTime-timestamp) * rate/1000);
// 判断是否超过最大桶容量
nowTokensNums = min(nowTokensNums);
System.out.println("当前的桶容量"+nowTokensNums);
// 修改拿令牌的时间
timestamp = nowTime;
// 判断是否可以拿到令牌
if (nowTokensNums < 1 ){
return false;
} else {
// 可以拿到令牌,令牌-1
nowTokensNums -= 1;
return true;
}
}
}
测试类:
package com.dsdj.limiting;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
/**
* TokenBucketTest
*
* @author cll
* @version 1.0
* @date 2019/10/14 10:12
**/
@Slf4j
public class TokenBucketTest {
private TokenBucket tokenBucket = new TokenBucket();
public static void main(String[] args) {
TokenBucketTest tokenBucketTest = new TokenBucketTest();
CountDownLatch countDownLatch = new CountDownLatch(200);
CountDownLatch countDownLatch2 = new CountDownLatch(200);
log.info("-------------------------------------");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<200;i++){
// 模拟并发请求
new Thread(() -> {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
String api = tokenBucketTest.api();
log.info("线程{}:{}",Thread.currentThread().getName(),api);
countDownLatch2.countDown();
},"线程"+i).start();
countDownLatch.countDown();
}
try {
countDownLatch2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String api() {
if (tokenBucket.getToken()){
return "接口访问成功";
} else {
return "----------------限流-----------";
}
}
}
输出
11:19:21.525 [main] INFO com.dsdj.limiting.TokenBucketTest - -------------------------------------
当前的桶容量12
11:19:22.719 [线程0] INFO com.dsdj.limiting.TokenBucketTest - 线程线程0:接口访问成功
当前的桶容量11
11:19:22.783 [线程1] INFO com.dsdj.limiting.TokenBucketTest - 线程线程1:接口访问成功
当前的桶容量10
11:19:22.790 [线程2] INFO com.dsdj.limiting.TokenBucketTest - 线程线程2:接口访问成功
当前的桶容量9
11:19:22.792 [线程3] INFO com.dsdj.limiting.TokenBucketTest - 线程线程3:接口访问成功
当前的桶容量8
11:19:22.793 [线程5] INFO com.dsdj.limiting.TokenBucketTest - 线程线程5:接口访问成功
当前的桶容量7
11:19:22.796 [线程7] INFO com.dsdj.limiting.TokenBucketTest - 线程线程7:接口访问成功
当前的桶容量6
11:19:22.798 [线程9] INFO com.dsdj.limiting.TokenBucketTest - 线程线程9:接口访问成功
当前的桶容量5
11:19:22.803 [线程11] INFO com.dsdj.limiting.TokenBucketTest - 线程线程11:接口访问成功
当前的桶容量4
11:19:22.804 [线程13] INFO com.dsdj.limiting.TokenBucketTest - 线程线程13:接口访问成功
当前的桶容量3
11:19:22.805 [线程15] INFO com.dsdj.limiting.TokenBucketTest - 线程线程15:接口访问成功
当前的桶容量2
11:19:22.806 [线程17] INFO com.dsdj.limiting.TokenBucketTest - 线程线程17:接口访问成功
当前的桶容量1
11:19:22.808 [线程19] INFO com.dsdj.limiting.TokenBucketTest - 线程线程19:接口访问成功
当前的桶容量0
11:19:22.809 [线程21] INFO com.dsdj.limiting.TokenBucketTest - 线程线程21:----------------限流-----------
当前的桶容量0
11:19:22.811 [线程23] INFO com.dsdj.limiting.TokenBucketTest - 线程线程23:----------------限流-----------
当前的桶容量0
11:19:22.812 [线程25] INFO com.dsdj.limiting.TokenBucketTest - 线程线程25:----------------限流-----------
Guava中RateLimiter
令牌桶的使用
maven依赖
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
Demo
package com.dsdj.limiting;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
/**
* RateLimiterTest
*
**/
@Slf4j
public class RateLimiterTest {
// 指定每秒放100个令牌
private RateLimiter rateLimiter = RateLimiter.create(100);
public static void main(String[] args) throws InterruptedException {
RateLimiterTest rateLimiterTest = new RateLimiterTest();
CountDownLatch countDownLatch = new CountDownLatch(200);
CountDownLatch countDownLatch2 = new CountDownLatch(200);
log.info("-------------------------------------");
Thread.sleep(5000);
for (int i=0;i<200;i++){
// 模拟并发请求
new Thread(() -> {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
String api = rateLimiterTest.api();
log.info("线程{}:{}",Thread.currentThread().getName(),api);
countDownLatch2.countDown();
},"线程"+i).start();
countDownLatch.countDown();
}
countDownLatch2.await();
}
public String api() {
if (rateLimiter.tryAcquire()){ // 拿一个
return "接口访问成功";
} else {
return "----------------限流-----------";
}
}
}