1. sentinel限流算法滑动时间窗源码剖析
2. sentinel限流算法漏桶与令牌桶详解
3. sentinel拦截器处理web请求源码剖析
在查看sentinel实现的滑动时间窗算法之前, 我们先自己实现一个这样的算法
前提要求 : 每一分钟只能通过一百的并发, 如果超过一百则进行熔断
看到这里, 大家可能马上就会想到这种实现方法
我们可以每60s统计一次, 判断每一个60s以内的请求数是否都小于100, 这样看起来并没有什么问题, 但是这样的统计是不准确的, 比如0-30s有40个请求, 30-60s有55个请求, 60-90s有55个请求,90-120s有40个请求, 那么这样算的话, 0-60s和60-120s都不会熔断, 但是实际上30-90s之间已经有了110个请求, 应该发生熔断, 因此我们需要重新进行设计
直接上代码
package com.hctrl.window;
import java.util.LinkedList;
import java.util.Random;
/**
* 滑动事件窗口限流实现
* 假设某个服务最多每秒钟处理100个请求, 我们可以设置一个1秒钟的滑动事件窗口
* 窗口中有10个格子, 每个格子100ms, 每100ms移动一次, 每次移动都需要记录当前服务的总次数
* @author hctrl
* @date 2021/12/21 20:20
*/
public class SlidingTimeWindow {
//服务访问次数, 可以放在redis中, 实现分布式系统的访问计数
private Long counter = 0L;
//使用LinkedList来记录滑动窗口的10个格子
private LinkedList<Long> slots = new LinkedList<>();
public static void main(String[] args) throws InterruptedException {
SlidingTimeWindow timeWindow = new SlidingTimeWindow();
new Thread(new Runnable() {
@Override
public void run() {
try {
timeWindow.doCheck();
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
while (true){
//todo 判断限流标志, 如果限流不向下执行
timeWindow.counter++;
Thread.sleep(new Random().nextInt(15));
}
}
private void doCheck() throws InterruptedException {
while (true){
Thread.sleep(100);
slots.addLast(counter);
long temp = 0L;
//保证slots的长度最大是11
if (slots.size() > 11){
temp = slots.getFirst();
slots.removeFirst();
}
if (slots.size() == 11){
//比较最后一个和第一个, 两者相差100以上就是限流
if((slots.peekLast() - temp) > 100){
System.out.println("限流了..");
//todo 修改限流标记为true
}else{
//todo 修改限流标记为false
}
}else{
//比较最后一个和第一个, 两者相差100以上就是限流
if(slots.peekLast() > 100){
System.out.println("限流了..");
//todo 修改限流标记为true
}else{
//todo 修改限流标记为false
}
}
}
}
}
上面代码就实现了滑动窗口代码, 接下来进行图解
0-100ms内有10个请求
100-200ms内有30个请求, 这30个请求包含0-100ms内的10个
当时间过了200ms, slots的长度没有大于10, 则slots的最后一个值就是这1s内的总并发量
当时间到了400ms, 120已经大于100了, 这进行熔断, 则从400-1000ms内的请求数都是120
当时间再过100ms, 图如下
0-100ms将会被移除
同时100-1100ms这一秒内的请求总数就变成了 120 - 10 = 110, 还是熔断状态
同理1100-1200之间的总请求数还是120, 100-200ms的30将会被移除
同时200-1200ms之间的总请求书变成了 120 - 30 = 90, 熔断状态关闭, 可以继续接受请求
这个就是滑动窗口的实现之一
但是这种还是会存在问题的, 因为有可能50-100ms有9个请求, 0-50ms有1个请求, 那么50-1050ms可能还会出现计算误差, 因此滑动窗口并不能百分之百的保证准确性, 时间粒度控制越小, 越准确
接下来就看一下sentinel底层是如何实现滑动窗口的
首先进入
StatisticSlot的entry方法
进入方法查看
可以看到sentinel对秒级, 分钟级两个维度进行统计, 我们只看秒级的
然我们先看看
rollingCounterInSecond是什么
new ArrayMetric() 进入这个方法
new OccupiableBucketLeapArray进入这个方法
看着这里 , 可以推断出sentinel实现滑动窗口的方法是通过一个长度为2的数组实现的, 数组中存储的是
WindowWrap<MetricBucket>对象, 他的value值就是MetricBucket对象, 在MetricBucket类中有一个变量是private final LongAdder[] counters;, 根据不同的索引存储不同状态的个数, 存储的值主要包括
图解 :
继续向下看
sentinel实现滑动窗口的整个逻辑都在这个方法中,
让我们先进入获取索引的方法
当前时间除以500, 结果值在对数组的长度取余, 如果当前时间是800, 除以500等于1, 1对2取余, 结果是1, 则800会落入数组索引为1的位置
进入获取开始时间的方法
当前时间 - 当前时间 % 500, 如果当前时间是800,则结果等于500,
接下来进入最关键的if方法, 就是在if方法中实现的滑动窗口
什么情况满足上面这张图呢? 比如这种情况
到此为止, 如果我们想计算1s以内的qps数量,
滑动窗口算法分析完毕
sentinel中他的数组长度指定为2, 通过不断的改变数组中WindowWrap<MetricBucket>对象的windowStart时间和counter中的pass值来实现滑动窗口, 毫无疑问, sentinel也是有误差的