Sentinel 限流、熔断降级源码架构图

微服务组件 Sentinel 源码解析 - 图1

常见限流算法

计数器法

计数器法是限流算法里最简单也是最容易实现的一种算法;es:规定,对于 A 接口来说,1 分钟的访问次数不能超过 100 个;那么可以真么做:在一开始的时候,可以设置一个计数器 counter,每当一个请求过来的时候,counter 就加 1,若 counter 的值大于 100 并且该请求与第一个请求的时间间隔还在 1 分钟之内,那么说明请求数过多了;若该请求与第一个请求的时间间隔大于 1 分钟,且 counter 的值还在限流范围内,那么就重置 counter

模拟代码:

  1. public class Counter {
  2. public long timeStamp = System.currentTimeMillis(); // 当前时间
  3. public int reqCount = 0; // 初始化计数器
  4. public final int limit = 100; // 时间窗口内最大请求数
  5. public final long interval = 1000 * 60; // 时间窗口ms
  6. public boolean limit() {
  7. long now = System.currentTimeMillis();
  8. if (now < timeStamp + interval) {
  9. // 在时间窗口内
  10. reqCount++;
  11. // 判断当前时间窗口内是否超过最大请求控制数
  12. return reqCount <= limit;
  13. } else {
  14. timeStamp = now;
  15. // 超时后重置
  16. reqCount = 1;
  17. return true;
  18. }
  19. }
  20. }

滑动时间窗口算法

滑动时间窗口又称 rolling window;为了解决计数器法统计精度太低的问题
image.png
如上图所示,整个红色的矩阵表示一个时间窗口,在例子中,一个时间窗口就是一分钟;然后将时间窗口进行划分,划分成了 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
image.png
白话解释:首先有一个固定容量的桶,有水流进来,也有水流出去;对于流进来的水来说,无法预计一共有多少水会流进来,也无法预计水流的速度;但对于流出去的水来说,这个桶可以固定水流出的速率;而且,当桶满了之后,多余的水将会溢出

模拟代码:

/**
 * 漏桶限流算法
 */
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
微服务组件 Sentinel 源码解析 - 图4
首先,有一个固定容量的桶,桶里存放这令牌(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;
        }
    }
}