Sentinel核心架构源码剖析(下)

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也是有误差的

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值