Disruptor学习总结(二):Disruptor分析

来源:互联网 发布:网络教学管理平台 编辑:程序博客网 时间:2024/05/21 11:20

一、RingBuffer相关

      在上文中我们介绍了RingBuffer的结构,这里我们就不再赘述了。它比操作数组更快,因为不用删除对象,数据对象可以复用,但是我们还存在几个疑问。整个生产/消费环境是怎样协调的?RingBuffer如何防止数据覆盖?接下来,我们就这两个问题进行分析。

1、生产/消费协调

      当生产者和消费者都只有一个时,两个线程分别操作不同的指针(一读一写),此时不需要锁。
      当有多个消费者时,每个消费者各自控制自己的读指针,依次读取每个slot,这时只需要保证生产者指针不会超过最慢的消费者即可,不需要锁。
      当有多个生产者时,多个线程共用一个写指针,此处需要考虑多线程问题。这时需要用CAS机制来保证各生产者的同步。

消费者

      消费者并不是直接操作RingBuffer的,而是通过ConsumerBarrier对象间接地操作RingBuffer。像生产者一样,Consumer要知道它的下一个读取需要才能读取。Consumer并不是一个个地读取数据,而是批量读取,举个栗子:如果它处理完了6号slot以前的数据,那么接下来它期待处理7号slot。ConsumerBarrier返回RingBuffer的最大可访问序号,假如是10。如果7号以后的slot还没有数据,那么它会根据ConsumerBarrier里面的WaitStrategy策略进行等待,直到生产者生产完数据。同时,生产者每次生产完一个数据之后都会通知Consumer,而不是Consumer每次都去询问。直到生产完10号slot的数据之后,Consumer才会一次性读取几个slot的数据,然后才更新自己的cursor。

生产者

      生产者同样不是直接操作RingBuffer的,它通过ProducerBarrier来访问RingBuffer。需要通过ProducerBarrier来获取下一个可写的slot,往里面填充数据。这里就会涉及到一个数据覆盖的问题,怎样确保Producer填充的slot已经被Consumer消费了呢?
      ConsumerTrackingProducerBarrier对象拥有所有正在访问RingBuffer的消费者列表。Disruptor由消费者负责通知它们处理到了哪个序列号,而不是RingBuffer。所以,如果想确定没有让Ring Buffer重叠,需要检查所有的消费者们都读到了哪里。Producer找到最小sequence消费者所操作的那个slot,如果Producer下一步操作正准备操作那个slot,ProducerBarrier 停下来自旋等待,直到那个消费者消费完一批数据后离开才继续。
      写数据分为两个阶段。首先获取下一个可写的slot,获取里面的Entry。然后往Entry里面填充数据,之后才commit将Entry填入RingBuffer中。接下来ProducerBarrier会让消费者知道 buffer 中有新东西了。它会通知 ConsumerBarrier上的 WaitStrategy对象。

二、Disruptor其它性能提高方式

      成批操作和避免GC(重用数据对象)我们在上文中都有介绍,接下来我们再介绍一种Disruptor的优化策略——缓存行填充。

      CPU缓存常以64bytes作为一个缓存行大小,缓存由若干个缓存行组成,缓存写回主存或主存写入缓存均是以行为单位,此外每个 CPU核心都有自己的缓存,若某个核心对某缓存行做出修改,其他拥有同样缓存的核心需要进行同步。
      在Disruptor中,生产者和消费者的指针都存在缓存行中。如果一个指针改变,那么同一个缓存行中的其他指针都会失效,此时需要从主存重新调配,这样很明显会消耗CPU。解决方案却非常简单:对于一个long型的缓冲区指针,用一个长度为8的long型数组代替。如此一来,一个缓存行被这个数 组填充满,线程对各自指针的修改不会干扰到其它指针。

三、Disruptor 的等待策略

      当消费者在SequenceBarrier上有许多可选的等待策略,不同的等待策略在延迟和CPU资源的占用上有所不同:

  1. BlockingWaitStrategy:是Disruptor的默认等待策略。这个策略的内部用锁和条件变量来控制线程的执行和等待。它是最慢的等待策略,但也是CPU使用率最低和最稳定的选项。
  2. SleepingWaitStrategy:SpleepingWaitStrategy的CPU使用率也比较低。它的方式是循环等待同时调用LockSupport的parkNanos()方法来睡眠。它的优点在于生产线程只需要计数,而不执行任何指令,但是延迟比较大。SleepingWaitStrategy最好用在不需要低延迟,而且事件发布对于生产者的影响比较小的情况下。
  3. YieldingWaitStrategy:YieldingWaitStrategy也是适合在低延迟系统中的策略,这种策略在减低系统延迟的同时也会增加CPU运算量。YieldingWaitStrategy策略会循环等待sequence增加到合适的值。循环中调用Thread.yield()允许其他准备好的线程执行。如果需要高性能而且事件消费者线程比逻辑内核少的时候,推荐使用YieldingWaitStrategy策略。
  4. BusySpinWaitStrategy:BusySpinWaitStrategy是性能最高的等待策略,同时也是对部署环境要求最高的策略。这个策略最好用在事件处理线程比物理内核数目还要小的时候。
原创粉丝点击