高可用之限流-05-slide window 滑动窗口

打印 上一主题 下一主题

主题 879|帖子 879|积分 2637

限流系列

开源组件 rate-limit: 限流
高可用之限流-01-入门介绍
高可用之限流-02-如何设计限流框架
高可用之限流-03-Semaphore 信号量做限流
高可用之限流-04-fixed window 固定窗口
高可用之限流-05-slide window 滑动窗口
高可用之限流-06-slide window 滑动窗口 sentinel 源码
高可用之限流-07-token bucket 令牌桶算法
高可用之限流 08-leaky bucket漏桶算法
高可用之限流 09-guava RateLimiter 入门使用简介 & 源码分析
滑动日志-Sliding Log

滑动日志算法,利用记载下来的用户的哀求时间,哀求数,当该用户的一个新的哀求进来时,比较这个用户在这个窗口内的哀求数是否超过了限定值,超过的话就拒绝这个哀求。
优点:


  • 避免了固定窗口算法在窗口界限大概出现的两倍流量问题
  • 由于是针对每个用户进行统计的,不会引发惊群效应
缺点:


  • 需要保存大量的哀求日志
  • 每个哀求都需要考虑该用户之前的哀求情况,在分布式体系中尤其难做到
时间比例

滑动窗口算法,联合了固定窗口算法的低开销和滑动日志算法能够办理的界限情况。

  • 为每个窗口进行哀求量的计数
  • 联合上一个窗口的哀求量和这一个窗口已经经过的时间来盘算出上限,以此平滑哀求尖锋
举例来说,限流的上限是每分钟 10 个哀求,窗口巨细为 1 分钟,上一个窗口中总共处理了 6 个哀求。
在假设这个新的窗口已经经过了 20 秒,那么 到现在为止允许的哀求上限就是 10 - 6 * (1 - 20 / 60) = 8。
滑动窗口算法是这些算法中最实用的算法:

  • 有很好的性能
  • 避免了漏桶算法带来的饥饿问题
  • 避免了固定窗口算法的哀求量突增的问题
ps: 这里是一种思绪,但却不是正宗的滑动窗口算法。
滑动窗口

滑动窗口将固定窗口再等分为多个小的窗口。

滑动窗口可以通过更细粒度对数据进行统计。
在限流算法里:假设我们将1s划分为4个窗口,则每个窗口对应250ms。
假设恶意用户照旧在上一秒的最后一刻和下一秒的第一刻打击服务,按照滑动窗口的原理,此时统计上一秒的最后750毫秒和下一秒的前250毫秒,这种方式能够判断出用户的访问仍旧超过了1s的访问数量,因此依然会阻拦用户的访问。
特点

滑动窗口具有以下特点:
1、每个小窗口的巨细可以均等,dubbo的默认负载均衡算法random就是通过滑动窗口设计的,可以调整每个每个窗口的巨细,进行负载。
2、滑动窗口的个数及巨细可以根据实际应用进行控制
滑动时间窗口

滑动时间窗口就是把一段时间片分为多个窗口,然后盘算对应的时间落在谁人窗口上,来对数据统计;
如上图其实就是即时的滑动时间窗口,随着时间流失,最开始的窗口将会失效,但是也会生成新的窗口;sentinel的就是通过这个原理来实时的限流数据统计。
关于滑动窗口,这里介绍照旧比较简单,重要是大致的介绍滑动的原理以实时间窗口的设计;其实关于滑动窗口在我们学习的盘算机网络中也涉及到。
java 实现

伪代码
  1. 全局数组 链表[]  counterList = new 链表[切分的滑动窗口数量];
  2. //有一个定时器,在每一次统计时间段起点需要变化的时候就将索引0位置的元素移除,并在末端追加一个新元素。
  3. int sum = counterList.Sum();
  4. if(sum > 限流阈值) {
  5.     return; //不继续处理请求。
  6. }
  7. int 当前索引 = 当前时间的秒数 % 切分的滑动窗口数量;
  8. counterList[当前索引]++;
  9. // do something...
复制代码
java 核心实现

该方法将时间直接切分为10分,然后逐步处理。
临时没有做更加细致的可设置化,后期考虑添加。
  1. /**
  2. * 全局的限制次数
  3. *
  4. * 固定时间窗口
  5. * @author houbinbin
  6. * Created by bbhou on 2017/9/20.
  7. * @since 0.0.5
  8. */
  9. public class LimitFixedWindow extends LimitAdaptor {
  10.     /**
  11.      * 日志
  12.      * @since 0.0.4
  13.      */
  14.     private static final Log LOG = LogFactory.getLog(LimitFixedWindow.class);
  15.     /**
  16.      * 上下文
  17.      * @since 0.0.4
  18.      */
  19.     private final ILimitContext context;
  20.     /**
  21.      * 计数器
  22.      * @since 0.0.4
  23.      */
  24.     private AtomicInteger counter = new AtomicInteger(0);
  25.     /**
  26.      * 限制状态的工具
  27.      *
  28.      * 避免不同线程的 notify+wait 报错问题
  29.      *
  30.      * @since 0.0.4
  31.      */
  32.     private CountDownLatch latch = new CountDownLatch(1);
  33.     /**
  34.      * 构造器
  35.      * @param context 上下文
  36.      * @since 0.0.4
  37.      */
  38.     public LimitFixedWindow(ILimitContext context) {
  39.         this.context = context;
  40.         // 定时将 count 清零。
  41.         final long interval = context.interval();
  42.         final TimeUnit timeUnit = context.timeUnit();
  43.         // 任务调度
  44.         ExecutorServiceUtil.singleSchedule(new Runnable() {
  45.             @Override
  46.             public void run() {
  47.                 initCounter();
  48.             }
  49.         }, interval, timeUnit);
  50.     }
  51.     @Override
  52.     public synchronized void acquire() {
  53.         // 超过阈值,则进行等待
  54.         if (counter.get() >= this.context.count()) {
  55.             try {
  56.                 LOG.debug("[Limit] fixed count need wait for notify.");
  57.                 latch.await();
  58.                 LOG.debug("[Limit] fixed count need wait end ");
  59.                 this.latch = new CountDownLatch(1);
  60.             } catch (InterruptedException e) {
  61.                 Thread.currentThread().interrupt();
  62.                 LOG.error("[Limit] fixed count is interrupt", e);
  63.             }
  64.         }
  65.         // 结束
  66.         int value = this.counter.incrementAndGet();
  67.         LOG.debug("[Limit] fixed count is " + value);
  68.     }
  69.     /**
  70.      * 初始化计数器
  71.      * @since 0.0.4
  72.      */
  73.     private void initCounter() {
  74.         LOG.debug("[Limit] fixed count init counter start");
  75.         // 通知可以继续执行(这里不能无脑 notify)会卡主
  76.         if(this.counter.get() >= this.context.count()) {
  77.             this.counter = new AtomicInteger(0);
  78.             LOG.debug("[Limit] fixed count notify all start");
  79.             latch.countDown();
  80.             LOG.debug("[Limit] fixed count notify all end");
  81.         }  else {
  82.             this.counter = new AtomicInteger(0);
  83.         }
  84.     }
  85. }
复制代码
基于 queue 的解法

另外一种解法,个人也是比较喜欢的。
直接创建一个队列,队列巨细便是限制的数量。
直接对比队首队尾的时间,从而保证固定当达到指定固定的次数时,时间一定是满意的。
ps: 这个后续在看看,不一定是滑动窗口的。
  1. public class LimitSlideWindowQueue extends LimitAdaptor {
  2.     private static final Log LOG = LogFactory.getLog(LimitSlideWindowQueue.class);
  3.     /**
  4.      * 用于存放时间的队列
  5.      * @since 0.0.3
  6.      */
  7.     private final BlockingQueue<Long> timeBlockQueue;
  8.     /**
  9.      * 当前时间
  10.      * @since 0.0.5
  11.      */
  12.     private final ICurrentTime currentTime = Instances.singleton(CurrentTime.class);
  13.     /**
  14.      * 等待间隔时间
  15.      * @since 0.0.5
  16.      */
  17.     private final long intervalInMills;
  18.     /**
  19.      * 构造器
  20.      * @param context 上下文
  21.      * @since 0.0.3
  22.      */
  23.     public LimitSlideWindowQueue(ILimitContext context) {
  24.         this.timeBlockQueue = new ArrayBlockingQueue<>(context.count());
  25.         this.intervalInMills = context.timeUnit().toMillis(context.interval());
  26.     }
  27.     @Override
  28.     public synchronized void acquire() {
  29.         long currentTimeInMills = currentTime.currentTimeInMills();
  30.         //1. 将时间放入队列中 如果放得下,直接可以执行。反之,需要等待
  31.         //2. 等待完成之后,将第一个元素剔除。将最新的时间加入队列中。
  32.         boolean offerResult = timeBlockQueue.offer(currentTimeInMills);
  33.         if(!offerResult) {
  34.             //获取队列头的元素
  35.             //1. 取出头节点,获取最初的时间
  36.             //2. 将头结点移除
  37.             long headTimeInMills = timeBlockQueue.poll();
  38.             //当前时间和头的时间差
  39.             long durationInMills = currentTimeInMills - headTimeInMills;
  40.             if(intervalInMills > durationInMills) {
  41.                 //需要沉睡的时间
  42.                 long sleepInMills = intervalInMills - durationInMills;
  43.                 DateUtil.sleep(sleepInMills);
  44.             }
  45.             currentTimeInMills = currentTime.currentTimeInMills();
  46.             boolean addResult = timeBlockQueue.offer(currentTimeInMills);
  47.             LOG.debug("[Limit] acquire add result: " + addResult);
  48.         }
  49.     }
  50. }
复制代码
参考资料

限流技术总结
固定窗口和滑动窗口算法相识一下
Sentinel之滑动时间窗口设计(二)
限流滑动窗口
限流算法之固定窗口与滑动窗口
限流--基于某个滑动时间窗口限流
【限流算法】java实现滑动时间窗口算法
谈谈高并发体系的限流
TCP协议的滑动窗口详细是怎样控制流量的?
漏铜令牌桶

漏桶算法&令牌桶算法理解及常用的算法
流量控制算法——漏桶算法和令牌桶算法
Token Bucket 令牌桶算法
华为-令牌桶算法
简单分析Guava中RateLimiter中的令牌桶算法的实现

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

知者何南

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表