文章目录

  • 1. Sentinel架构大致流程
  • 2. Sentinel断路器的三种状态
  • 3. 计算QPS的限流算法
    • ①:计数器限流
    • ②:滑动时间窗算法限流
    • ③:漏桶算法限流
    • ④:令牌桶限流

1. Sentinel架构大致流程

  1. Sentinel其实就是一个AOP,通过AspectJ切入要进行限流的接口,为其添加@Around环绕通知,并使用try-catch包裹起来,源码在SentinelAutoConfiguration

  2. 每一个对该限流接口的请求,都要经过AOP的增强,先执行过一系列流控、熔断规则组成的责任链,然后才执行真正的接口逻辑。责任链的组装使用了原生的spi机制,流控规则可以在sentinel控制台去配置,配置完毕后会填入sentinel服务端,也就是我们的某一个服务,请求流控接口时,就会触发流控逻辑!

    @Aspect //使用的AOP
    public class SentinelResourceAspect extends AbstractSentinelAspectSupport {//切入点@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")public void sentinelResourceAnnotationPointcut() {}//环绕通知@Around("sentinelResourceAnnotationPointcut()")public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {Method originMethod = resolveMethod(pjp);//1.获取 @SentinelResource 注解SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);if (annotation == null) {// Should not go through here.throw new IllegalStateException("Wrong state for SentinelResource annotation");}String resourceName = getResourceName(annotation.value(), originMethod);EntryType entryType = annotation.entryType();int resourceType = annotation.resourceType();Entry entry = null;try {// 2.执行流控组成的责任链entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());// 3.执行业务方法Object result = pjp.proceed();return result;} catch (BlockException ex) {//4. 抛出流控异常return handleBlockException(pjp, annotation, ex);} catch (Throwable ex) {// 5. 抛出业务异常Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();// The ignore list will be checked first.if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {throw ex;}if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {traceException(ex);return handleFallback(pjp, annotation, ex);}// No fallback function can handle the exception, so throw it out.throw ex;} finally {if (entry != null) {//6.所有流控的收尾逻辑,比如:断路器半开状态重试entry.exit(1, pjp.getArgs());}}}
    }
    
  3. 如果执行中抛出异常,异常分为流控异常BlockException 和 业务异常Throwable,流控异常可以使用blockHandler进行处理,业务异常使用fallback处理!

    @RequestMapping(value = "/findOrderByUserId/{id}")
    @SentinelResource(value = "findOrderByUserId",//业务异常,ExceptionUtil类中的fallback方法来处理fallback = "fallback",fallbackClass = ExceptionUtil.class, //流控异常,ExceptionUtil类中的handleException方法来处理blockHandler = "handleException",blockHandlerClass = ExceptionUtil.class)
    public R  findOrderByUserId(@PathVariable("id") Integer id) {//ribbon实现String url = "http://xx/order/findOrderByUserId/"+id;R result = restTemplate.getForObject(url,R.class);if(id==4){throw new IllegalArgumentException("非法参数异常");}return result;
    }
    
    public class ExceptionUtil {//业务异常public static R fallback(Integer id,Throwable e){return R.error(-2,"===被异常降级啦===");}//流控异常public static R handleException(Integer id, BlockException e){return R.error(-2,"===被限流啦===");}
    }
    
  4. Sentinel中统计单位时间QPS进行流控时,采用的是滑动时间窗算法!流控效果有三种

    • 快速失败,直接抛出流控异常,底层使用的是滑动时间窗口算法
    • 预热Warm up,把突然爆发的大流量变为缓慢增加
    • 匀速排队,使用的漏桶算法
  5. Sentinel中服务熔断降级有三个指标

    • 慢调用比例
    • 异常比例
    • 异常个数

2. Sentinel断路器的三种状态

  • Sentinel中服务熔断降级的断路器有三个状态,分别是关闭(close)、打开(open)和半开(halfOpen)状态。

    • 如果在单位时间内达到断路条件,则把断路器置为打开(open)状态,抛出流控异常,进行服务熔断
    • 下一次请求过来时,如果断路器是关闭(close)状态,直接通行;如果是打开(open)状态,则会查看当前时间是否大于断路后的最小等待时间,如果大于则把断路器置为半开(halfOpen)状态;如果小于,继续阻塞
    • 最后会在try-catch-finallyfinally中判断断路器的状态是否是半开(halfOpen)状态,如果是,则请求一次接口,如果请求正常,则把断路器置为打开(open)状态,如果不正常把断路器置为关闭(close)状态

3. 计算QPS的限流算法

①:计数器限流

计数器法是限流算法里最简单也是最容易实现的一种算法。对于A接口来说,1分钟的访问次数不能超过100个。

那么可以这么做:

  • 在一开始的时候,我们可以设置一个计 数器counter,每当一个请求过来的时候,counter就加1,
  • 如果counter的值大于100并且该请求 与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;
  • 如果该请求与第一个请求的间 隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter

计数器实现限流的缺点就是:精度低,如果在 0.5分钟 和 1.5 分钟之间有超过100个请求,这种限流算法就不起作用了。此外,计数器限流算法的实现还可以使用redis,设置一个key,一分钟过期,进来一个请求就使用incr命令自增一,代码中拿key的值与limt进行比较!


public class _10_限流算法_计数器 {//开始统计时间private long beginTime = System.currentTimeMillis();//请求数private int reqCount = 0;//请求限制数private int limit = 100;//单位时间:1分钟private long window = 1000 * 60;public boolean limitReq() {//当前时间long currTime = System.currentTimeMillis();if (currTime < beginTime + window) {//如果当前时间在统计期内,则递增请求数,并于限制数100比较reqCount++;return reqCount <= limit;} else {//如果当前时间不在统计期内,则重置请求数,并设置当前时间为统计开始时间reqCount = 1;beginTime = currTime;return true;}}
}

②:滑动时间窗算法限流

为了解决计数器法统计精度太低的问题,引入了滑动窗口算法。滑动时间窗其实就是把计数器限流算法的时间窗口再做进一步划分,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。Sentinel底层在做统计QPS做快速失败时用的也是滑动时间窗算法

滑动时间窗口限流实现:

  • 假设某个服务最多只能每秒钟处理100个请求,可以设置一个1秒钟的滑动时间窗口,用LinkedList表示,该窗口分为10个格子。
  • 每个格子100毫秒,每100毫秒移动一次,每次移动都需要记录当前服务100ms内请求的次数counter到格子中,counter的值是累计请求的值。不会被重置
  • 如果格子数大于10个,删除最前边的各自,格子数始终保留10个
  • 用最后一个格子的counter值减去最前边格子的counter值,如果大于限流请求数,则会被限流。否则不做限流
public class _11_限流算法_滑动时间窗 {//服务访问次数,可以放在Redis中,实现分布式系统的访问计数Long counter = 0L;//使用LinkedList来记录滑动窗口的10个格子。LinkedList<Long> slots = new LinkedList<Long>();public static void main(String[] args) throws InterruptedException {_11_限流算法_滑动时间窗 timeWindow = new _11_限流算法_滑动时间窗();//开启一个子线程执行滑动时间窗检测请求个数new Thread(new Runnable() {@Overridepublic 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) {//每过100ms,就把总请求数加入linkedList末尾节点,该linkedList长度也自增一slots.addLast(counter);//如果linkedList长度大于10个,则删除最前面的一个,体现滑动时间窗if (slots.size() > 10) {slots.removeFirst();}//比较最后一个节点和第一个节点,两者相差100以上就限流if ((slots.peekLast() - slots.peekFirst()) > 100) {System.out.println("限流了。。");//TODO 修改限流标记为true} else {//TODO 修改限流标记为false}//每100毫秒执行一次Thread.sleep(100);}}
}

Sentinel底层在做统计QPS做快速失败时用的也是滑动时间窗算法,只不过与上面的稍有不同

  • Sentinel在做QPS统计时,滑动时间窗有两个维度

    • 毫秒级维度:初始化一个跨度为1000ms,包含两个500ms的时间窗口
    • 秒级维度:还有一个跨度为60s的,包含601s的时间窗口

在毫秒级维度中,仅仅使用两个时间窗口就完成了QPS的计算,并没有做删除时间窗口节点的操作,而是清空原本节点的内容。两个时间窗口代表数组的两个下标。

  • 通过 (当前时间 / 500ms) % 数组长度2的取模结果,得到当前时间的请求数落在那个时间窗口内
  • 然后拿 (当前时间 / 500ms) * 500ms比较时间窗口的起始位置,
    • 如果与之前 (比如:500ms) 一致,就把当次请求数加入到该窗口内用作统计
    • 如果与之前不一致,就清空之前时间窗口内统计的数据,并放入当前时间的请求数,完成滑动的操作

③:漏桶算法限流

首先,需要有一个固定容量的桶,有水流进来,也有水流出去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。

将算法中的水换成实际应用中的请求,就可以看到漏桶算法天生就限制了请求的速度。 当使用了漏桶算法,可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题。

public class _12_限流算法_漏桶算法 {//初始时间private long initTime = System.currentTimeMillis();//漏桶算法一般都有三个指标 桶的容量 流出速度 当前水位private long capacity; //容量,代表最大接受请求个数private long rate;  //水流速度private long water; //当前水位 ,桶内剩余请求数public boolean limit() {//当有请求进入时的时间long now = System.currentTimeMillis();//计算一下当时水位:请求进来之间一直在匀速滴水,当前水位要减去这部分滴出去的水!water = Math.max(0, water - ((now - initTime) / 1000) * rate);if (water + 1 <= capacity) {//如果当前水位没满,返回true,并把当前水位+1water += 1;return true;} else {//否则返回false,水满了 不让进!return false;}}}

④:令牌桶限流

令牌桶算法,又称token bucket。同样为了理解该算法,我们来看一下该算法的示意图:

       从图中我们可以看到,令牌桶算法比漏桶算法稍显复杂。首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token以 一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。 因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。

伪代码:

/*** 令牌桶限流算法*/
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;}}
}

Sentinel计算QPS限流算法相关推荐

  1. Sentinel(四)限流算法-令牌桶算法

    Sentinel中使用的令牌桶算法,是参考着Guava中的令牌桶算法来的.所以在此之前有必要分析下Guava中的限流算法.参见https://www.cnblogs.com/krock/p/16347 ...

  2. Sentinel限流算法详解(硬啃)

    文章目录 常见四种限流算法 固定窗口计数器 滑动窗口计数器 漏桶(也有称漏斗 Leaky bucket) 令牌桶( Token bucket) Sentinel源码举例 滑动窗口 漏桶 令牌桶 常见四 ...

  3. Sentinel滑动时间窗限流算法

    Sentinel系列文章 Sentinel熔断限流器工作原理 Sentinel云原生K8S部署实战 Sentinel核心源码解析 时间窗限流算法 如图 10-20这个时间窗内请求数量是60小于阈值10 ...

  4. Sentinel滑动时间窗限流算法原理及源码解析(上)

    文章目录 时间窗限流算法 滑动时间窗口 滑动时间窗口算法改进 滑动时间窗口源码解析 时间窗限流算法 10t到16t 10个请求 16t-20t 50个请求 20t-26t 60个请求 26t到30t ...

  5. Sentinel整合Dubbo限流实战

    Sentinel整合Dubbo限流实战 创建provider项目 添加jar依赖 <dependency><artifactId>sentinel-api</artifa ...

  6. 常见的限流算法与实现

    限流的实现 常见的限流算法: 限流是对某一时间窗口内的请求数进行限制,保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机. 常见的限流算法有三种: 计数器限流(固定窗口) 原理: 时间 ...

  7. 5种限流算法,7种限流方式,挡住突发流量?

    大家好啊,我是阿朗,最近工作中需要用到限流,这篇文章介绍常见的限流方式. 文章持续更新,可以关注公众号程序猿阿朗或访问未读代码博客. 本文 Github.com/niumoo/JavaNotes 已经 ...

  8. 5种限流算法,7种限流方式,挡住突发流量

    前言 最近几年,随着微服务的流行,服务和服务之间的依赖越来越强,调用关系越来越复杂,服务和服务之间的稳定性越来越重要.在遇到突发的请求量激增,恶意的用户访问,亦或请求频率过高给下游服务带来较大压力时, ...

  9. 搞懂限流算法这一篇就够了

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 TL;DR(too long don't read) 限流算法:计 ...

最新文章

  1. 华为、北大、悉尼大学:最新视觉Transformer综述(2017-2020年)
  2. 【Kotlin】Lambda 表达式 ( 简介 | 表达式语法 | 表达式类型 | 表达式返回值 | 调用方式 | 完整示例 )
  3. [Angular 2] Template property syntax
  4. C++11新特性学习
  5. c++模板--2(模板机制,模板的局限性,类模板,类模板做函数的参数)
  6. 如何优雅地「蜗居」?
  7. c语言中width获取窗体宽度,获取屏幕宽高width(),outerWidth,innerWidth,clientWidth的区别...
  8. java内置排序有哪些_内部排序比较(Java版)
  9. 移动端数据爬取和Scrapy框架
  10. Linux的capability分析
  11. @Retryable和@Recover的使用
  12. 阿里云与线下IDC对接IPsec虚拟专用网络
  13. 人生感悟|写在四月底
  14. 树莓派4B官方说明文档
  15. android UI Tab切页效果 总结
  16. 【大数据】SQL的作业题(仅适合初学者)
  17. 203、商城业务-商品详情-环境搭建
  18. 如何实现广告精准投放?一文获得新思路
  19. NVMe over TCP Write/Read命令下发流程梳理
  20. 预告:殿堂级男神洪小文即将开课: AI不为人知的威力

热门文章

  1. 物流系统管理课程(二十一)
  2. AttributeError: module transformers has no attribute LLaMATokenizer解决方案
  3. toolbar标题居中android,Toolbar实现标题居中效果
  4. VS2008的破解方法
  5. 使用POI实现导出功能
  6. 数据结构栈(顺序栈、链栈、插入push、删除pop)、队(循环队,链队、入队push,出队pop)知识点梳理
  7. 混乱的亚马逊测评市场,商家如何长久生存?
  8. python的web应用程序开发(Django框架)
  9. Java内存结构内存模型
  10. 计算机用电安全知识,计算机机房用电安全制度