介绍
令牌桶算法:
一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌;如下:
a.假设是2r/s,则每500毫秒添加n个令牌;
b.桶中最多存放x个令牌,桶满时,再添加会拒绝;
c.请求去获取令牌,如果令牌足够,允许通过;如果令牌不足,拒绝请求或放入缓冲区等待;
漏桶算法:
漏桶容量固定,按照固定速率流出水滴;如果桶是空的,不用流,如果桶满了,再流入水滴,则拒绝服务。
区别
令牌桶控制的是平均流入速率,速率可高可低,允许有突发请求;漏桶控制的是恒定的流出速率,从而平滑流入速率。
java实现
令牌桶算法:
package com.cvnavi.oa.report;
import org.apache.kafka.common.protocol.types.Field;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TokenLimiter {
private ArrayBlockingQueue<String> blockingQueue;
//容量大小
private int limit;
//令牌的产生间隔
private int period;
//令牌每次产生的个数
private int amount;
public TokenLimiter(int limit, int period, int amount) {
this.limit = limit;
this.period = period;
this.amount = amount;
blockingQueue = new ArrayBlockingQueue<>(limit);
}
private void init() {
for(int i = 0; i < limit; i++) {
blockingQueue.add("lp");
}
}
private void addToken(int amount) {
for(int i = 0; i < amount; i++) {
//溢出返回false
blockingQueue.offer("lp");
}
}
/**
* 获取令牌
* @return
*/
public boolean tryAcquire() {
//队首元素出队
return blockingQueue.poll() != null ? true : false;
}
/**
* 生产令牌
*/
private void start(Object lock) {
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
synchronized (lock) {
addToken(this.amount);
lock.notify();
}
}, 500, this.period, TimeUnit.MILLISECONDS);
}
/**
* 先生产2个令牌,减少4个令牌;再每500ms生产2个令牌,减少4个令牌
*/
public static void main(String[] args) throws InterruptedException {
int period = 500;
TokenLimiter limiter = new TokenLimiter(8, period, 2);
limiter.init();
Object lock = new Object();
limiter.start(lock);
//让线程先产生2个令牌(溢出)
synchronized (lock) {
lock.wait();
}
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 4; j++) {
String s = i + "," + j + ":";
if(limiter.tryAcquire()) {
System.out.println(s + "拿到令牌");
}
else{
System.out.println(s + "拒绝");
}
}
Thread.sleep(period);
}
}
}
漏桶算法:
package com.cvnavi.report;
public class Funnel {
//漏斗容量
int capacity;
//漏水速度
double leakingRate;
//漏斗剩余空间(不是水的大小)
int leftQuota;
//上次漏斗漏水的时间
long leakingTs;
public Funnel(int capacity, double leakingRate, int leftQuota, long leakingTs) {
this.capacity = capacity;
this.leakingRate = leakingRate;
this.leftQuota = leftQuota;
this.leakingTs = leakingTs;
}
private void makeSpace() {
long nowTs = System.currentTimeMillis();
//这个是间隔的时间
long deltaTs = nowTs - this.leakingTs;
//漏掉的水
int deltaQuota = (int)(deltaTs * leakingRate);
//间隔时间太长,溢出
if (deltaQuota < 0){
this.leftQuota = capacity;
this.leakingTs = nowTs;
return;
}
//说明漏的时间不够
if (deltaQuota < 1) {
return;
}
this.leakingTs = nowTs;
this.leftQuota = this.leftQuota + deltaQuota;
if (this.leftQuota > this.capacity) {
this.leftQuota = this.capacity;
}
}
/**
* 流入
* @param quota
* @return
*/
public Boolean water(int quota) {
makeSpace();
//表示量充足
if (leftQuota >= quota){
leftQuota = leftQuota - quota;
return true;
}
//剩余量不够
return false;
}
}
package com.cvnavi.report;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class FunnelLimitRate {
private static Map<String,Funnel> funnels = new HashMap<>();
public static void main(String[] args) throws InterruptedException {
String userId= "2019";
String actionKey = "rgister";
int capacity = 8;
double leakingRate = 0.001;
int leftQuota = 5;
Funnel funnel = funnels.get(userId);
if (funnel == null) {
funnel = new Funnel(capacity, leakingRate, leftQuota, System.currentTimeMillis());
}
//每1000ms(1s),流入1的水
//每1000ms,流出1000*leakingRate(而且要>1)的水
for (int i = 0; i < 8; i++) {
Boolean isBoolean = funnel.water(1);
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println(isBoolean + " " + funnel.leftQuota);
}
}
}