Sentinel 限流、熔断降级源码架构图
常见限流算法
计数器法
计数器法是限流算法里最简单也是最容易实现的一种算法;es:规定,对于 A 接口来说,1 分钟的访问次数不能超过 100 个;那么可以真么做:在一开始的时候,可以设置一个计数器 counter,每当一个请求过来的时候,counter 就加 1,若 counter 的值大于 100 并且该请求与第一个请求的时间间隔还在 1 分钟之内,那么说明请求数过多了;若该请求与第一个请求的时间间隔大于 1 分钟,且 counter 的值还在限流范围内,那么就重置 counter
模拟代码:
public class Counter {
public long timeStamp = System.currentTimeMillis(); // 当前时间
public int reqCount = 0; // 初始化计数器
public final int limit = 100; // 时间窗口内最大请求数
public final long interval = 1000 * 60; // 时间窗口ms
public boolean limit() {
long now = System.currentTimeMillis();
if (now < timeStamp + interval) {
// 在时间窗口内
reqCount++;
// 判断当前时间窗口内是否超过最大请求控制数
return reqCount <= limit;
} else {
timeStamp = now;
// 超时后重置
reqCount = 1;
return true;
}
}
}
滑动时间窗口算法
滑动时间窗口又称 rolling window;为了解决计数器法统计精度太低的问题
如上图所示,整个红色的矩阵表示一个时间窗口,在例子中,一个时间窗口就是一分钟;然后将时间窗口进行划分,划分成了 6 格,每格就代表 10 秒;每过 10s,时间窗口就会往右滑动一格;每一个格子都有独立的计数器 counter,es:一个请求在 0:35s 的时候到达,那么 0:30~0:39 对应的 counter 就会加 1;当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精准
模拟代码:
/**
* 滑动时间窗口限流实现
* 假设某个服务最多只能每秒钟处理100个请求,我们可以设置一个1秒钟的滑动时间窗口,
* 窗口中有10个格子,每个格子100毫秒,每100毫秒移动一次,每次移动都需要记录当前服务请求的次数
*/
public class SlidingTimeWindow {
//服务访问次数,可以放在Redis中,实现分布式系统的访问计数
Long counter = 0L;
//使用LinkedList来记录滑动窗口的10个格子。
LinkedList<Long> slots = new LinkedList<Long>();
public static void main(String[] args) throws InterruptedException {
SlidingTimeWindow timeWindow = new SlidingTimeWindow();
new Thread(new Runnable() {
@Override
public void run() {
try {
timeWindow.doCheck();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
while (true){
//TODO 判断限流标记
timeWindow.counter++;
Thread.sleep(new Random().nextInt(15));
}
}
private void doCheck() throws InterruptedException {
while (true) {
slots.addLast(counter);
if (slots.size() > 10) {
slots.removeFirst();
}
//比较最后一个和第一个,两者相差100以上就限流
if ((slots.peekLast() - slots.peekFirst()) > 100) {
System.out.println("限流了。。");
//TODO 修改限流标记为true
}else {
//TODO 修改限流标记为false
}
Thread.sleep(100);
}
}
}
漏桶算法
漏桶算法又称 leaky bucket
白话解释:首先有一个固定容量的桶,有水流进来,也有水流出去;对于流进来的水来说,无法预计一共有多少水会流进来,也无法预计水流的速度;但对于流出去的水来说,这个桶可以固定水流出的速率;而且,当桶满了之后,多余的水将会溢出
模拟代码:
/**
* 漏桶限流算法
*/
public class LeakyBucket {
public long timeStamp = System.currentTimeMillis(); // 当前时间
public long capacity; // 桶的容量
public long rate; // 水漏出的速度(每秒系统能处理的请求数)
public long water; // 当前水量(当前累积请求数)
public boolean limit() {
long now = System.currentTimeMillis();
water = Math.max(0, water - ((now - timeStamp)/1000) * rate); // 先执行漏水,计算剩余水量
timeStamp = now;
if ((water + 1) < capacity) {
// 尝试加水,并且水还未满
water += 1;
return true;
} else {
// 水满,拒绝加水
return false;
}
}
}
令牌桶算法
令牌桶算法,又称 token bucket
首先,有一个固定容量的桶,桶里存放这令牌(token);桶一开始是空的,token 以一个固定的速率 r 往桶里填充,知道达到桶的容量,多余的令牌将会被丢弃;每当一个请求过来时,就会尝试从桶里移除一个令牌,若没有令牌的话,请求无法通过
模拟代码:
/**
* 令牌桶限流算法
*/
public class TokenBucket {
public long timeStamp = System.currentTimeMillis(); // 当前时间
public long capacity; // 桶的容量
public long rate; // 令牌放入速度
public long tokens; // 当前令牌数量
public boolean grant() {
long now = System.currentTimeMillis();
// 先添加令牌
tokens = Math.min(capacity, tokens + (now - timeStamp) * rate);
timeStamp = now;
if (tokens < 1) {
// 若不到1个令牌,则拒绝
return false;
} else {
// 还有令牌,领取令牌
tokens -= 1;
return true;
}
}
}