目录
一、漏桶(Leaky Bucket)
简易实现:
效果:
二、令牌桶算法
简易实现:
效果:
两种算法对比:
一、漏桶(Leaky Bucket)
算法思路简单,水(请求)先进入到漏桶中,漏桶以一定的速度出水(接口有响应速率),当水流速度过大直接溢出(访问频率超过接口响应频率),然后就拒绝请求,可以看出漏桶算法能强制限制数据的传输速率。
假设我们有一个桶,我们希望以随机的速率向桶中倒水,并以恒定的速率从桶中取水。因此我们需要在桶底部打一个固定大小的洞,这样来保证水流处的速率是恒定的。由于桶的体积有限,因此倒满后就停止。
水流入的速率可以改变,但输出的速率是恒定的。这种平滑输出流量的方法称为漏桶技术。突发进入的流量存储在桶中,并以稳定的速率输出。
简易实现:
package com.suanfa;
public class test2 {
//漏桶算法
public static void main(String[] args) {
//t1,t2线程每1秒往桶里添加一个请求
Bucket bucket=new Bucket(0);
Thread t1=new Thread(bucket);
Thread t2=new Thread(bucket);
//t3线程恒定速度处理请求
Thread t3=new Thread(new Handle(bucket));
t1.start();
t2.start();
t3.start();
}
}
class Bucket implements Runnable{
int max=20;//桶的最大容量
int num;//请求数
Bucket(int num){
this.num=num;
}
@Override
public void run() {
while (true){
synchronized (this){
if (num>=max){
System.out.println("桶的容量满了,该请求被丢失");
}else {
num++;
System.out.println("往桶里添加一个请求,目前有"+num+"个请求");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//处理请求
class Handle implements Runnable{
Bucket b;
Handle(Bucket b){
this.b=b;
}
@Override
public void run() {
while (true){
synchronized (b){
if (b.num>0){
b.num--;
System.out.println("已处理一个请求,目前还剩"+b.num+"个请求");
}else {
System.out.println("桶已空,不需要处理请求");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
效果:
桶中请求数有限制,满了则丢弃,桶以一定速度处理请求

二、令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
令牌桶算法和漏桶算法不同的是,有时后端能够处理一定的突发情况,只是为了系统稳定,一般不会让请求超过正常情况的60%,给容灾留有余地。但漏桶算法中后端处理速度是固定的,对于短时的突发情况,后端不能及时处理,和实际处理能力不匹配。
令牌算法是以固定速度往一个桶内增加令牌,当桶内令牌满了后,就停止增加令牌。上游请求时,先从桶里拿一个令牌,后端只服务有令牌的请求,所以后端处理速度不一定是匀速的。当有突发请求过来时,如果令牌桶是满的,则会瞬间消耗桶中存量的令牌。如果令牌还不够,那么再等待发放令牌(固定速度),这样就导致处理请求的速度超过发放令牌的速度。
简易实现:
package com.suanfa;
public class test{
//令牌桶算法
public static void main(String[] args) {
Token t=new Token(20);//初始20个
//t1每秒往桶里放令牌
Thread t1=new Thread(t);
//t3,t4模拟请求,处理一个请求需要从桶里获取一个
// 令牌,没有令牌,就丢弃
Thread t3=new Thread(new Use("a",t));
Thread t4=new Thread(new Use("b",t));
t1.start();
t3.start();
t4.start();
}
}
//往桶里放令牌
class Token implements Runnable {
//令牌数
int token;
Token(int token){
this.token=token;
}
@Override
public void run() {
while(true){
synchronized (this){
token++;
System.out.println("往桶里放一个令牌,目前共有"+token+"个令牌");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Use implements Runnable {
//请求名
String name;
Token t;
Use(String name,Token t){
this.t=t;
this.name=name;
}
@Override
public void run() {
while(true){
synchronized (t){
if (t.token>0){
t.token--;
System.out.println(name+"在桶里获取1个令牌,还剩"+t.token+"个令牌");
}else {
System.out.println("目前桶里无令牌,请求失效");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
效果:
处理请求需要令牌,没有令牌则丢弃该请求

两种算法对比:
漏桶算法通常可以用于限制访问外部接口的流量,保护其他人系统,比如我们请求银行接口,通常要限制并发数。
令牌桶算法生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,可以处理瞬时流量,而且拿令牌的过程并不是消耗很大的事情。令牌桶算法通常可以用于限制被访问的流量,保护自身系统。