常见的限流算法
计数器算法
计数器算法指在一段时间内,进行计数,与阀值进行比较,如果超过了阀值则进行限流操作,到了时间临界点,将计数器清零进行重新基数,即单位时间段内可访问请求的次数进行控制。计数器算法是一种比直观简单的限流算法,常用于应用服务对外提供的接口层面。
由于计数器算法存在时间临界点缺陷,因此在时间临界点左右的极短时间段内容易遭到攻击。比如设定每分钟最多可以请求100次某个接口,如12:00:00-12:00:59时间段内没有数据请求,而12:00:59-12:01:00时间段内突然并发100次请求,而紧接着跨入下一个计数周期,计数器清零,在12:01:00-12:01:01内又有100次请求。那么也就是说在时间临界点左右可能同时有2倍的阀进行请求,从而造成后台处理请求过载的情况,导致系统运营能力不足,甚至导致系统崩溃。
滑动窗口算法
滑动窗口算法是指把固定时间片进行划分,并且随着时间的流逝进行移动,通过这种方式可以巧妙的避开计数器的临界点的问题。也就是说这些固定数量的可以移动的格子,将会进行计数判断阀值,因此格子的数量影响着滑动窗口算法的精度。
滑动窗口算法可以有效的规避计数器算法中时间临界点的问题,但是仍然存在时间片段的概念。同时滑动窗口算法计数运算也相对计数器算法比较耗时。时间片段越精确,流量控制越精密,从而导致计算耗时越长。
漏桶算法
漏桶算法设计思路比较直观简单,即水(请求)以不确定的速率先进入到漏桶里,然后漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。因此漏桶算法在这方面比滑动窗口而言,更加先进。
漏桶算法需要设定两个参数,一个是桶的容量大小用,用来决定最多可以存放多少水(请求),一个是水桶漏的大小,即出水速率,即出水速率决定单位时间向服务器请求的平均次数。因此漏桶算法存在如下两个问题:
漏桶的漏出速率是固定的参数,所以即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发到端口速率提高。因此漏桶算法对于存在突发特性的流量来说缺乏效率。漏桶的漏出速率是固定的参数同时水桶容量大小限制,那么意味着如果瞬时流量增大且超过水桶水桶容量的话,将有部分请求被丢弃掉(也就是所谓的溢出)。
令牌桶算法
令牌桶算法是和水桶算法效果一样但方向相反的算法。随着时间流逝,系统会按恒定时间间隔往桶里加入制定数量的水,如每100毫秒往桶里加入1000毫升的水,如果桶已经满了就不再加了(说明:令牌桶算法和漏桶算法相反,漏桶算法是按照客户请求的数量往漏桶中加水的,而令牌桶算法是服务器端控制往桶里加水的)。当有新请求来临时,会各自从桶里拿走一个令牌,如果没有令牌可拿了就阻塞或者拒绝服务。
令牌桶算法的优点如下:
服务器端可以根据实际服务性能和时间段改变改变生成令牌的速度和水桶的容量。 一旦需要提高速率,则按需提高放入桶中的令牌的速率。生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味着当面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情。
算法总计:以上四种算法均可以达到流量控制的目的,不论是对于令牌桶拿不到令牌被拒绝,还是漏桶的水满了溢出,都是为了保证大部分流量的正常使用,而牺牲掉了少部分流量,这是符合设计规范和运营需求且合理的算法。如因极少部分流量需要保证可能导致系统达到极限而挂掉,则得不偿失。
总结:
计数器算法: 对单位时间内可访问的请求次数进行控制。
滑动窗口:把固定的时间片进行划分,并且随着时间的流逝进行移动,每个时间片内进行计算器算法。
漏桶算法:将请求放入一个固定大小的桶内,然后以一定的速度对桶内的请求进行处理。当桶溢出时候,直接拒绝请求。
令牌桶算法:服务端生成令牌,然后放入桶内,如果桶已经满了则就不再加了,如果请求过来,就从桶内拿走一个令牌,如果桶内五令牌可拿,则进行阻塞或拒绝服务。
令牌桶算法实现
利用谷歌的rateLimiter实现
import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* rate limiter
* Created by zhangwy on 2019/12/25 0017.
*/
public class RateLimiterTest {
RateLimiter rateLimiter = RateLimiter.create(2.0);
ExecutorService executor = Executors.newFixedThreadPool(5);
ExecutorService executor2 = Executors.newFixedThreadPool(5);
final Logger logger = LoggerFactory.getLogger(getClass());
AtomicLong sum = new AtomicLong(0);
@Test
public void test() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < 10000; i++) {
executor.submit(new ThreadRunner("name"+ i,rateLimiter, sum, latch));
}
for (int i = 0; i < 10000; i++) {
executor2.submit(new ThreadRunner("name"+ i,rateLimiter, sum, latch));
}
latch.countDown();
Thread.currentThread().sleep(10000 );
new CountDownLatch(1).await();
}
class ThreadRunner implements Runnable {
RateLimiter rateLimiter;
AtomicLong sum;
Random random = new Random();
String name;
CountDownLatch latch;
public ThreadRunner(String threadName, RateLimiter rateLimiter, AtomicLong sum, CountDownLatch latch) {
this.rateLimiter = rateLimiter;
this.sum = sum;
this.latch = latch;
this.name = threadName;
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("ThreadRunner "+ name);
if(rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
logger.info("run " );
// sum.decrementAndGet();
} else {
logger.info("限流 ");
}
long current = sum.incrementAndGet();
}
}
}
利用信号量实现
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* semaphore wrapper包装类
* (基于信号量实现限流,可替换成别的实现)
* 带超时时间
*/
public class SemaphoreWrapper {
final protected Semaphore semaphore;
protected int timeout;
protected int permits;
public SemaphoreWrapper(int permits, int timeout) {
this.permits = permits;
semaphore = new Semaphore(permits);
this.timeout = timeout;
}
public boolean tryAcquire() throws InterruptedException {
return semaphore.tryAcquire(timeout, TimeUnit.SECONDS);
}
public void release() {
semaphore.release();
}
}
import com.google.common.util.concurrent.Monitor;
import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* rate limiter
*/
public class RateLimiterBucketTest {
RateLimiter rateLimiter = RateLimiter.create(2.0);
ExecutorService executor = Executors.newFixedThreadPool(10);
ExecutorService executor2 = Executors.newFixedThreadPool(5);
final Logger logger = LoggerFactory.getLogger(getClass());
AtomicLong sum = new AtomicLong(0);
@Test
public void test() throws InterruptedException {
RateBucket<String> rateBucket = new RateBucket();
CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < 10; i++) {
executor.submit(new produceThreadRunner(rateBucket, "element "+ i , latch));
}
for (int i = 0; i < 5; i++) {
executor2.submit(new consumerThreadRunner(rateBucket, sum, latch));
}
latch.countDown();
// Thread.currentThread().sleep(10000 );
new CountDownLatch(1).await();
}
class produceThreadRunner implements Runnable {
private final RateBucket rateBucket;
private final String element;
private final CountDownLatch latch;
public produceThreadRunner(RateBucket<String> rateBucket, String element, CountDownLatch latch) {
this.rateBucket = rateBucket;
this.element = element;
this.latch = latch;
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true) {
try {
//以一定的速率定时提交数据
rateBucket.offer(element);
Thread.currentThread().sleep(100L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class consumerThreadRunner implements Runnable {
private final RateBucket rateBucket;
private final AtomicLong sum;
private final CountDownLatch latch ;
public consumerThreadRunner(RateBucket<String> rateBucket, AtomicLong sum, CountDownLatch latch ) {
this.rateBucket = rateBucket;
this.sum = sum;
this.latch= latch;
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true) {
try {
//以一定的速率定时消费数据
Object element = rateBucket.comsumer();
if(null != element) {
logger.info("element {}", element);
sum.incrementAndGet();
}
else {
logger.info("error {}", element);
}
Thread.currentThread().sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class RateBucket<T> {
ConcurrentLinkedQueue<T> container = new ConcurrentLinkedQueue<>(); //水桶 也是容器
private int BUCKET_SIZE = 15;
private Monitor produceMonitor = new Monitor(); //不满的清空下可以放入容器中
private Monitor consumerMonitor = new Monitor(); //不空的情况下可以从容器读取数据
Monitor.Guard guard = new Monitor.Guard(produceMonitor) {
@Override
public boolean isSatisfied() {
//容器不满
return container.size() < BUCKET_SIZE;
}
};
Monitor.Guard consumerGuard = new Monitor.Guard(consumerMonitor) {
@Override
public boolean isSatisfied() {
//容器不空
return !container.isEmpty();
}
};
public boolean offer(T element ) {
//加锁操作,判断容器不满则放入数据
if(produceMonitor.enterIf(guard)){
try {
//放入数据
container.offer(element);
return true;
} finally {
//释放锁
produceMonitor.leave();
}
}else {
logger.info("限流");
return false;
}
}
public T comsumer() {
// try {
// consumerMonitor.enterWhen(consumerGuard);
// return container.poll();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }finally {
// consumerMonitor.leave();
// }
//加锁操作,判断容器不空则取数据
if (consumerMonitor.enterIf(consumerGuard)) {
try {
//取容器中的数据
return container.poll();
} finally {
//释放数据
consumerMonitor.leave();
}
} else {
logger.info("容器为空");
return null;
}
}
}
}
import com.google.common.util.concurrent.Monitor;
import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* rate limiter
*/
public class RateLimiterBucketTest {
RateLimiter rateLimiter = RateLimiter.create(2.0);
ExecutorService executor = Executors.newFixedThreadPool(10);
ExecutorService executor2 = Executors.newFixedThreadPool(5);
final Logger logger = LoggerFactory.getLogger(getClass());
AtomicLong sum = new AtomicLong(0);
@Test
public void test() throws InterruptedException {
RateBucket<String> rateBucket = new RateBucket();
CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < 10; i++) {
executor.submit(new produceThreadRunner(rateBucket, "element "+ i , latch));
}
for (int i = 0; i < 5; i++) {
executor2.submit(new consumerThreadRunner(rateBucket, sum, latch));
}
latch.countDown();
// Thread.currentThread().sleep(10000 );
new CountDownLatch(1).await();
}
class produceThreadRunner implements Runnable {
private final RateBucket rateBucket;
private final String element;
private final CountDownLatch latch;
public produceThreadRunner(RateBucket<String> rateBucket, String element, CountDownLatch latch) {
this.rateBucket = rateBucket;
this.element = element;
this.latch = latch;
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true) {
try {
//以一定的速率定时提交数据
rateBucket.offer(element);
Thread.currentThread().sleep(1000L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class consumerThreadRunner implements Runnable {
private final RateBucket rateBucket;
private final AtomicLong sum;
private final CountDownLatch latch ;
public consumerThreadRunner(RateBucket<String> rateBucket, AtomicLong sum, CountDownLatch latch ) {
this.rateBucket = rateBucket;
this.sum = sum;
this.latch= latch;
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true) {
try {
//以一定的速率定时消费数据
Object element = rateBucket.comsumer();
if(null != element) {
logger.info("element {}", element);
sum.incrementAndGet();
}
else {
logger.info("error {}", element);
}
Thread.currentThread().sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class RateBucket<T> {
ConcurrentLinkedQueue<T> container = new ConcurrentLinkedQueue<>(); //水桶 也是容器
private int BUCKET_SIZE = 15;
private Monitor monitor = new Monitor(); //不满的清空下可以放入容器中
// private Monitor consumerMonitor = new Monitor(); //不空的情况下可以从容器读取数据
Monitor.Guard guard = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
//容器不满
return container.size() < BUCKET_SIZE;
}
};
Monitor.Guard consumerGuard = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
//容器不空
return !container.isEmpty();
}
};
public boolean offer(T element ) {
//加锁操作,判断容器不满则放入数据
if(monitor.enterIf(guard)){
try {
//放入数据
container.offer(element);
return true;
} finally {
//释放锁
monitor.leave();
}
}else {
logger.info("限流");
return false;
}
}
public T comsumer() {
try {
monitor.enterWhen(consumerGuard);
return container.poll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
monitor.leave();
}
return null;
}
}
}