Disruptor高性能之道—AvailableBuffer的原理
系列文章:
Disruptor高性能之道—开篇&介绍
Disruptor高性能之道—False Sharing(伪共享)
Disruptor高性能之道—无锁实现(CAS)
Disruptor高性能之道—内存屏障(Volatile)
Disruptor高性能之道—AvailableBuffer的原理
目录
一、前言
相信熟悉Disruptor源码的同学都知道,Disruptor是通过环数组列来实现消息存储的。生产者每生产一个消息就在环形数组中向前移动一格。消费者的消费游标只要在生产者的后面就表示有可用消息可以消费。但是在多生产者模式下,当生产者生成消息的时候,并不是直接在环形数组中向前移动游标,而是通过availableBuffer来实现的。那么availableBuffer是如何实现的呢,本文将为大家一层一层揭开其神秘面纱。
二、正文
1、为什么要使用availableBuffer
在多线程的模式下,假如我们还是通过普通的在环形数组中向前移动一格来实现生产消息的功能,我们看看会有什么问题。
如果有两个线程(A和B)分别生产消息,两种方法实现:
方案1:
A获取到槽位后,在产生消息之前,一直持有生产者游标。等到生成完消息之后再释放。接着B操作。
方案2:
A和B能够并发使用游标,A和B都获得了自己需要的坑位。然后写入数据。
方案1中,其基本功能没有什么问题,但是且把多消费者变成了一个串行生产者了。这样会要生产效率变得很低。
方案2中,虽然A和B可以并发操作游标,并写入数据。但是有个问题,因为游标已经移动了此时如果消费者来获取数据,而A和B又没有准备好数据怎么办呢?
为了解决这个问题,Disruptor则采用了availableBuffer来解决这个问题,同时确保了多生成者的高效。
2、availableBuffer实现原理
其实和上诉的方案2一样,disruptor允许多消费者并发使用游标。只是其并不会通过生产者游标来判断哪个槽位的数据可以,而是额外提供了一个和Disruptor消息环一样大小的数组availableBuffer。是否可用是通过这个availableBuffer数组来确认的。
首先availableBuffer是一个Long类的数组,每个位置用来标记其对用的数据槽位是否可用。在availableBuffer中通过sequence/buffer.size这个数字编号来区分当前槽位是否可用(注意:生产者和消费者的sequence会一直递增下去)。消费者第一次在改槽位发布数据槽位数据位0 (sequence < size),第二次为1 (size < sequence < 2*size),以此类推。即消费者第一轮(所有槽位都发布了可用数据)发布之后,槽位值都为0;第二轮发布之后,所有槽位只都为1,以此类推。听起来是不是很懵逼,我们直接上图。
图例说明:
-
生产者发布到了第二轮,但是第二轮没有发布完。
-
生产者的游标cursor=11(即sequence=11)
-
当前最大消费者的sequence=11(即所有可用消息都消费完了)
-
当前最小消费者的sequence=8(即还在消费第一轮的数据)
-
其中绿色部分表示槽位的数据被所有消费者都消费过了,即生产者可以使用
-
灰色部分,表示数据只是部分消费者消费量了,生产者不能使用
假设,此时有多个生产者获取到了槽位(但还未publish数据,即还未准备好)。如下图:生产者游标cursor=16。粉色部分表示其还未publish数据,此时的availableBuffer中的数字还是0。即表示数据不可消费(因为生产者还没有publish消息)。因为最大消费者的sequence = 11,那么它向前移动为12,12/size = 1。所以他会认为availableBuffer中为0的是不可用的。
接着又生产者准备好消息,并publish了。如下图:
其中黄色部分表示槽位的数据已经发布了,且availableBuffer中的值页变成了1。但是仔细看,黄色的区域并不连续。大家可以想想为什么。
那么如下图:槽位2和3的数据就可用了,其会被max消费者消费掉。但是槽位5的数据必须要等待槽位4的数据publish之后,才能够被消费。因为消费者只能够连续消费,不能跨槽位消费。
Disruptor就是通过AvailableBuffer这样通过一轮一轮的标记,来记录哪些槽位的信息是可用的。从而实现了多生产者的高效并发生产消息。
遗留思考:Disruptor如何解决多消费者使用同一个游标产生的竞争问题?
三、惯例
如果你对本文有任何疑问或者高见,欢迎添加公众号共同交流探讨(添加公众号可以获得”Java高级架构“上10G的视频和图文资料哦)。