乐趣区

关于云计算:disruptor笔记之七等待策略

欢送拜访我的 GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,波及 Java、Docker、Kubernetes、DevOPS 等;

《disruptor 笔记》系列链接

  1. 疾速入门
  2. Disruptor 类剖析
  3. 环形队列的根底操作(不必 Disruptor 类)
  4. 事件生产知识点小结
  5. 事件生产实战
  6. 常见场景
  7. 期待策略
  8. 知识点补充(终篇)

本篇概览

本文是《disruptor 笔记》的第七篇,咱们一起浏览源码,学习一个重要的知识点:期待策略,因为 Disruptor 的源码短小精干、简略易懂,因而本篇是个轻松愉快的源码学习之旅;

提前小结

如果您工夫不富余,能够通过以下提前小结的内容,对期待策略有个大体的意识:

  1. BlockingWaitStrategy:用了 ReentrantLock 的期待 && 唤醒机制实现期待逻辑,是默认策略,比拟节俭 CPU
  2. BusySpinWaitStrategy:继续自旋,JDK9 之下慎用(最好别用)
  3. DummyWaitStrategy:返回的 Sequence 值为 0,失常环境是用不上的
  4. LiteBlockingWaitStrategy:基于 BlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作,然而作者说测试不充沛,不倡议应用
  5. TimeoutBlockingWaitStrategy:带超时的期待,超时后会执行业务指定的解决逻辑
  6. LiteTimeoutBlockingWaitStrategy:基于 TimeoutBlockingWaitStrategy,在没有锁竞争的时候会省去唤醒操作
  7. SleepingWaitStrategy:三段式,第一阶段自旋,第二阶段执行 Thread.yield 交出 CPU,第三阶段睡眠执行工夫,重复的的睡眠
  8. YieldingWaitStrategy:二段式,第一阶段自旋,第二阶段执行 Thread.yield 交出 CPU
  9. PhasedBackoffWaitStrategy:四段式,第一阶段自旋指定次数,第二阶段自旋指定工夫,第三阶段执行 Thread.yield 交出 CPU,第四阶段调用成员变量的 waitFor 办法,这个成员变量能够被设置为 BlockingWaitStrategy、LiteBlockingWaitStrategy、SleepingWaitStrategy 这三个中的一个

对于期待策略

  • 回顾一下后面的文章中实例化 Disruptor 的代码:
disruptor = new Disruptor<>(new OrderEventFactory(),
                BUFFER_SIZE,
                new CustomizableThreadFactory("event-handler-"));
  • 开展上述构造方法,会见到创立 RingBuffer 的代码,默认应用了 BlockingWaitStrategy 作为期待策略:
    public static <E> RingBuffer<E> createMultiProducer(EventFactory<E> factory, int bufferSize)
    {return createMultiProducer(factory, bufferSize, new BlockingWaitStrategy());
    }
  • 持续开展下面的 <font color=”blue”>createMultiProducer</font> 办法,可见每个 Sequencer(留神不是 Sequence)都有本人的 watStrategy 成员变量:

  • 这个 waitStrategy 的最终用处是创立 SequenceBarrier 的时候,传给 SequenceBarrier 做成员变量:

  • 在看看 SequenceBarrier 是如何应用 waitStrategy 的,一共两处用到,第一处如下图红框,原来是 <font color=”blue”>waitFor</font> 办法外部会用到,这个 <font color=”blue”>waitFor</font> 咱们后面曾经理解过,对消费者来说,期待环形队列的指定地位有可用数据时,就是调用 SequenceBarrier 的 waitFor 实现的:

  • SequenceBarrier 第二处用到 waitStrategy 是唤醒的时候:
    @Override
    public void alert()
    {
        alerted = true;
        waitStrategy.signalAllWhenBlocking();}
  • 当初咱们晓得了 WaitStrategy 的应用场景,接下来看看这个接口有哪些具体实现吧,这样咱们在编程中就晓得如何抉择才最适宜本人

BlockingWaitStrategy

  • 作为默认的期待策略,BlockingWaitStrategy 还有个特点就是代码量小(不到百行),很容易了解,其实就是用 ReentrantLock+Condition 来实现期待和唤醒操作的,如下图红框:

  • 如果您更偏向于节俭 CPU 资源,对高吞吐量和低延时的要求绝对低一些,那么 BlockingWaitStrategy 就适宜您了;

BusySpinWaitStrategy(慎用)

  • 后面的 BlockingWaitStrategy 有个特点,就是一旦环形队列指定地位来了数据,因为线程是期待状态(底层调用了 native 的 UNSAFE.park 办法),因而还要唤醒后能力执行业务逻辑,在一些场景中心愿数据一到就尽快生产,此时 BusySpinWaitStrategy 就很适合了,代码太简略,全副贴出:
public final class BusySpinWaitStrategy implements WaitStrategy
{
    @Override
    public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier)
        throws AlertException, InterruptedException
    {
        long availableSequence;

        while ((availableSequence = dependentSequence.get()) < sequence)
        {barrier.checkAlert();
            ThreadHints.onSpinWait();}

        return availableSequence;
    }

    @Override
    public void signalAllWhenBlocking()
    {}}
  • 上述代码显示,整个 while 循环的要害就是 <font color=”blue”>ThreadHints.onSpinWait</font> 做了什么,源码如下,这里要分外留神,如果 <font color=”blue”>ON_SPIN_WAIT_METHOD_HANDLE</font> 为空,意味着里面的 while 循环是个 <font color=”red”>十分耗费 CPU 的自旋</font>:
    public static void onSpinWait()
    {if (null != ON_SPIN_WAIT_METHOD_HANDLE)
        {
            try
            {ON_SPIN_WAIT_METHOD_HANDLE.invokeExact();
            }
            catch (final Throwable ignore)
            {}}
    }
  • ON_SPIN_WAIT_METHOD_HANDLE 为空是很可怕的事件,咱们来看看它是何方神圣?代码还是在 ThreadHints.java 中,如下所示,假相高深莫测,<font color=”blue”> 它就是 Thread 类的 onSpinWait 办法 </font>,如果 Thread 类没有 onSpinWait 办法,那么应用 BusySpinWaitStrategy 作为期待策略就有很高的代价了,环形队列里没有数据时生产线程会执行自旋,很消耗 CPU:
static
    {final MethodHandles.Lookup lookup = MethodHandles.lookup();

        MethodHandle methodHandle = null;
        try
        {methodHandle = lookup.findStatic(Thread.class, "onSpinWait", methodType(void.class));
        }
        catch (final Exception ignore)
        { }

        ON_SPIN_WAIT_METHOD_HANDLE = methodHandle;
    }
  • 好吧,还剩两个问题:Thread 类有没有 onSpinWait 办法还不能确定吗?这个 onSpinWait 办法是何方神圣?
  • 去看 JDK 官网文档,如下图,原来这办法是从 JDK9 才有的,所以对于 JDK8 使用者来说来说,选用 BusySpinWaitStrategy 就意味着要面对没做啥事儿的 while 循环了:

  • 第二个问题,onSpinWait 办法干了些啥?后面的官网文档,以欣宸的英语水平显然是无奈了解的,去看 stackoverflow 吧,如下图,简略的说,就是通知 CPU 以后线程处于循环查问的状态,CPU 得悉后就会调度更多 CPU 资源给其余线程:

  • 至此真像大白:环形队列的条件就绪后,BusySpinWaitStrategy 策略是通过 whlie 死循环来做到疾速响应的,如果 JDK 是 9 或者更高版本,这个死循环带来的 CPU 损耗由 Thread.onSpinWait 帮忙缓解,如果 JDK 版本低于 9,这里就是个简略的 while 死循环,至于这种死循环有多耗费 CPU,您能够写段简略代码感受一下 …
  • 难怪 Disruptor 源码中会揭示 <font color=”blue”> 最好是将应用此实例的线程绑定到指定 CPU 核 </font>:

DummyWaitStrategy

固定返回 0,集体感觉这个策略在失常开发中用不上,因为环形队列可用地位始终是 0 的话,不论是生产还是生产都难以实现:

LiteBlockingWaitStrategy

  • 看名字,LiteBlockingWaitStrategy 是 BlockingWaitStrategy 策略的轻量级实现,在锁没有竞争的时候 (例如独立生产的场景),会省略掉唤醒操作,不过如下图红框所示,作者说他没有充沛验证过正确性,因而倡议只用于体验,太好了,这个策略 <font color=”red”> 我不学了!!!</font>

TimeoutBlockingWaitStrategy

  • 顾名思义,TimeoutBlockingWaitStrategy 示意只期待某段时长,超过了就算超时,其代码和 BlockingWaitStrategy 相似,只是期待的时候有个时长限度,如下图,高深莫测:

  • 其实我对抛出异样后的解决很感兴趣,去看看吧,里面是相熟的 BatchEventProcessor 类,相熟的 processEvents 办法,如下图,每次超时异样都交给 <font color=”blue”>notifyTimeout</font> 解决,而内部的主流程不受影响,仍旧一直的从环形队列中期待和获取数据:

  • 进入 notifyTImeout 办法,可见实际上是交给成员变量 <font color=”blue”>timeoutHandler</font> 去解决的,而且处理过程中产生的任何异样都会被捕捉,不会抛出去影响内部调用:

  • 再来看看成员变量是哪来的,如下图,水落石出,咱们开发的 EventHandler 实现类,如果也实现了 Timeouthandler,就被当做成员变量 timeoutHandler 了:

  • 至此 TimeoutBlockingWaitStrategy 也搞清楚了:用于有工夫限度的场景,每次期待超时后都会调用业务定制的超时解决逻辑,这个逻辑写到 EventHandler 实现类中,这个实现类要实现 Timeouthandler 接口

LiteTimeoutBlockingWaitStrategy

  • LiteTimeoutBlockingWaitStrategy 与 TimeoutBlockingWaitStrategy 的关系,就像 BlockingWaitStrategy 与 LiteBlockingWaitStrategy 的关系:作为 TimeoutBlockingWaitStrategy 的变体,有 TimeoutBlockingWaitStrategy 的超时解决个性,而且没有锁竞争的时候,省略掉唤醒操作;
  • 作者说 LiteBlockingWaitStrategy 可用于体验,但正确性并未通过充沛验证,然而在 LiteTimeoutBlockingWaitStrategy 的正文中 <font color=”red”>没有看到这种说法</font>,看样子这是个靠谱的期待策略,能够用,用在有超时解决的需要,而且没有锁竞争的场景(例如独立生产)

SleepingWaitStrategy

  • 和后面几个不同的是,SleepingWaitStrategy 没有用到锁,这象征这无需调用 signalAllWhenBlocking 办法做唤醒解决,相当于省去了生产线程的告诉操作,官网源码正文有这么句话引起了我的趣味,如下图红框,粗心是该策略在性能和 CPU 资源耗费之间获得了均衡,接下来去看看要害代码,来理解这个个性:

  • 如下图,等到可用数据的过程是个死循环:

  • 接下来是要害代码了,如下图,可见整个期待过程分为三段:计数器高于 100 时就只有一个减一的操作(最快响应),计数器在 100 到 0 之间时每次都交出 CPU 执行工夫(最省资源),其余时候就睡眠固定工夫:

YieldingWaitStrategy

  • 看过 SleepingWaitStrategy 之后,再看 YieldingWaitStrategy 就很容易了解了,和 SleepingWaitStrategy 相比,YieldingWaitStrategy 先做指定次数的自旋,而后一直的交出 CPU 工夫:

  • 因为在一直的执行 <font color=”blue”>Thread.yield()</font> 办法,因而该策略尽管很耗费 CPU,不过一旦其余线程有 CPU 需要,很容易从这个线程失去;

PhasedBackoffWaitStrategy

  • 最初是 PhasedBackoffWaitStrategy,该策略的特点是将整个期待过程分成下图的四段,四个方块代表一个工夫线上的四个阶段:

  • 这里阐明一下上图的四个阶段:
  1. 首先是自旋指定的次数,默认 10000 次;
  2. 自旋过后,开始带计时的自旋,执行的时长是 spinTimeoutNanos 的值;
  3. 执行时长达到 spinTimeoutNanos 的值后,开始执行 <font color=”blue”>Thread.yield()</font> 交出 CPU 资源,这个逻辑的执行时长是 <font color=”red”>yieldTimeoutNanos-spinTimeoutNanos</font>;
  4. 执行时长达到 <font color=”red”>yieldTimeoutNanos-spinTimeoutNanos</font> 的值后,开始调用 <font color=”blue”>fallbackStrategy.waitFor</font>,这个调用没有工夫或者次数限度;
  5. 当初问题来了 <font color=”blue”>fallbackStrategy</font> 是何方神圣?PhasedBackoffWaitStrategy 类筹备了三个静态方法,咱们能够按需选用,让 fallbackStrategy 是 BlockingWaitStrategy、LiteBlockingWaitStrategy、SleepingWaitStrategy 这三个中的一个:
public static PhasedBackoffWaitStrategy withLock(
        long spinTimeout,
        long yieldTimeout,
        TimeUnit units)
    {
        return new PhasedBackoffWaitStrategy(
            spinTimeout, yieldTimeout,
            units, new BlockingWaitStrategy());
    }

public static PhasedBackoffWaitStrategy withLiteLock(
        long spinTimeout,
        long yieldTimeout,
        TimeUnit units)
    {
        return new PhasedBackoffWaitStrategy(
            spinTimeout, yieldTimeout,
            units, new LiteBlockingWaitStrategy());
    }

    public static PhasedBackoffWaitStrategy withSleep(
        long spinTimeout,
        long yieldTimeout,
        TimeUnit units)
    {
        return new PhasedBackoffWaitStrategy(
            spinTimeout, yieldTimeout,
            units, new SleepingWaitStrategy(0));
    }
  • 至此,Disruptor 的九种期待策略就全副剖析结束了,除了选用期待策略的时候更加得心应手,还有个播种就是积攒了浏览优良源码的教训,在读源码的路上更加有信念了;

你不孤独,欣宸原创一路相伴

  1. Java 系列
  2. Spring 系列
  3. Docker 系列
  4. kubernetes 系列
  5. 数据库 + 中间件系列
  6. DevOps 系列

欢送关注公众号:程序员欣宸

微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游 Java 世界 …
https://github.com/zq2599/blog_demos

退出移动版