计数器
package com.itheima.limit;
import java.util.concurrent.*;
public class Counter {
public static void main(String[] args) {
//计数器,这里用信号量实现
final Semaphore semaphore = new Semaphore(3);
//定时器,到点清零
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
semaphore.release(3);
}
},3000,3000,TimeUnit.MILLISECONDS);
//模拟无数个请求从天而降
while (true) {
try {
//判断计数器
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果准许响应,打印一个ok
System.out.println("ok");
}
}
}
3)结果分析
3个ok一组呈现,到下一个计数周期之前被阻断
4)优缺点
实现起来非常简单。
控制力度太过于简略,假如1s内限制3次,那么如果3次在前100ms内已经用完,后面的900ms将只能处于阻塞状态,白白浪费掉。
5)应用
使用计数器限流的场景较少,因为它的处理逻辑不够灵活。最常见的可能在web的登录密码验证,输入错误次数冻结一段时间的场景。如果网站请求使用计数器,那么恶意攻击者前100ms吃掉流量计数,使得后续正常的请求被全部阻断,整个服务很容易被搞垮。
漏桶算法
package com.itheima.limit;
import java.util.concurrent.*;
public class Barrel {
public static void main(String[] args) {
//桶,用阻塞队列实现,容量为3
final LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue(3);
//定时器,相当于服务的窗口,2s处理一个
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
int v = que.poll();
System.out.println("处理:"+v);
}
},2000,2000,TimeUnit.MILLISECONDS);
//无数个请求,i 可以理解为请求的编号
int i=0;
while (true) {
i++;
try {
System.out.println("put:"+i);
//如果是put,会一直等待桶中有空闲位置,不会丢弃
// que.put(i);
//等待1s如果进不了桶,就溢出丢弃
que.offer(i,1000,TimeUnit.MILLISECONDS);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3)结果分析
image-20200604162448941
put任务号按照顺序入桶
执行任务匀速的1s一个被处理
因为桶的容量只有3,所以1-3完美执行,4被溢出丢弃,5正常执行
4)优缺点
有效的挡住了外部的请求,保护了内部的服务不会过载
内部服务匀速执行,无法应对流量洪峰,无法做到弹性处理突发任务
任务超时溢出时被丢弃。现实中可能需要缓存队列辅助保持一段时间
5)应用
nginx中的限流是漏桶算法的典型应用,配置案例如下:
http {
#$binary_remote_addr 表示通过remote_addr这个标识来做key,也就是限制同一客户端ip地址。
#zone=one:10m 表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息。
#rate=1r/s 表示允许相同标识的客户端每秒1次访问
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /limited/ {
#zone=one 与上面limit_req_zone 里的name对应。
#burst=5 缓冲区,超过了访问频次限制的请求可以先放到这个缓冲区内,类似代码中的队列长度。
#nodelay 如果设置,超过访问频次而且缓冲区也满了的时候就会直接返回503,如果没有设置,则所有请求会等待排队,类似代码中的put还是offer。
limit_req zone=one burst=5 nodelay;
}
}
令牌桶
package com.itheima.limit;
import java.util.concurrent.*;
public class Token {
public static void main(String[] args) throws InterruptedException {
//令牌桶,信号量实现,容量为3
final Semaphore semaphore = new Semaphore(3);
//定时器,1s一个,匀速颁发令牌
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (semaphore.availablePermits() < 3){
semaphore.release();
}
// System.out.println("令牌数:"+semaphore.availablePermits());
}
},1000,1000,TimeUnit.MILLISECONDS);
//等待,等候令牌桶储存
Thread.sleep(5);
//模拟洪峰5个请求,前3个迅速响应,后两个排队
for (int i = 0; i < 5; i++) {
semaphore.acquire();
System.out.println("洪峰:"+i);
}
//模拟日常请求,2s一个
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
semaphore.acquire();
System.out.println("日常:"+i);
Thread.sleep(1000);
}
//再次洪峰
for (int i = 0; i < 5; i++) {
semaphore.acquire();
System.out.println("洪峰:"+i);
}
//检查令牌桶的数量
for (int i = 0; i < 5; i++) {
Thread.sleep(2000);
System.out.println("令牌剩余:"+semaphore.availablePermits());
}
}
}
3)结果分析
image-20200604162855096
注意结果出现的节奏!
洪峰0-2迅速被执行,说明桶中暂存了3个令牌,有效应对了洪峰
洪峰3,4被间隔性执行,得到了有效的限流
日常请求被匀速执行,间隔均匀
第二波洪峰来临,和第一次一样
请求过去后,令牌最终被均匀颁发,积累到3个后不再上升
4)应用
springcloud中gateway可以配置令牌桶实现限流控制,案例如下:
cloud:
gateway:
routes:
- id: limit_route
uri: http://localhost:8080/test
filters:
- name: RequestRateLimiter
args:
#限流的key,ipKeyResolver为spring中托管的Bean,需要扩展KeyResolver接口
key-resolver: '#{@ipResolver}'
#令牌桶每秒填充平均速率,相当于代码中的发放频率
redis-rate-limiter.replenishRate: 1
#令牌桶总容量,相当于代码中,信号量的容量
redis-rate-limiter.burstCapacity: 3