前面几篇文章,分别介绍了Disruptor的生产和消费逻辑,其中不乏“Sequence”的身影,生产者使用其记录生产的位置,防止出现数据覆盖;多个消费者通过其记录消费位置,可以管理自己的消费进度;消费者和生产者之间通过 SequenceBarrier保持生产和消费的平衡,即消费者不能超过生产的进度,这篇文章将会深入的分析下,Sequence到底是个什么东东。
一、温故知新
带大家再次回顾前文,看看生产者和消费者是如何借助sequencer实现生产和消费的进度管理的。
1.生产者
public <A> boolean tryPublishEvent(EventTranslatorOneArg<E, A> translator, A arg0){
try{
// 通过sequencer,获取自己写入的位置
final long sequence = sequencer.tryNext();
// 将事件写入RingBuffer
translateAndPublish(translator, sequence, arg0);
return true;
}
catch (InsufficientCapacityException e){
return false;
}
}
首先生产者调用tryPublishEvent方法,进行事件的发布,可以看到调用 sequencer.tryNext() 获取自己能写入的位置。
private <A> void translateAndPublish(EventTranslatorOneArg<E, A> translator, longsequence, A arg0){
try{
translator.translateTo(get(sequence), sequence, arg0);
}finally{
sequencer.publish(sequence);
}
}
然后get(sequence)获取到自己能用的“事件传递器”,把想要发布的事件交给“事件传递器”,最后调用sequencer.publish(sequence) 发布序号。
2.消费者
EventHandlerGroup<T> createEventProcessors(final Sequence[] barrierSequences,final EventHandler<? super T>[] eventHandlers){
... ...
// SequenceBarrier是由ringBuffer内部的序号管理器Sequencer创建的
// SequenceBarrier和生产进度代表者Sequencer就关联起来了
final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);
for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
{
// 传给BatchEventProcessor,多个BatchEventProcessor会公用一个SequenceBarrier
final BatchEventProcessor<T> batchEventProcessor =
new BatchEventProcessor<T>(ringBuffer, barrier, eventHandler);
}
... ...
}
EventHandlerGroup<T> createWorkerPool(final Sequence[] barrierSequences, final WorkHandler<? super T>[] workHandlers){
// 同样也是ringBuffer创建的SequenceBarrier
final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(barrierSequences);
final WorkerPool<T> workerPool = new WorkerPool<T>(ringBuffer, sequenceBarrier, exceptionHandler, workHandlers);
... ...
return new EventHandlerGroup<T>(this, consumerRepository, workerSequences);
}
public WorkerPool(final RingBuffer<T> ringBuffer,final SequenceBarrier sequenceBarrier,final ExceptionHandler<? super T> exceptionHandler,final WorkHandler<? super T>... workHandlers){
for (int i = 0; i < numWorkers; i++){
// 多个workProcessors共享sequenceBarrier和workSequence
workProcessors[i] = new WorkProcessor<T>(ringBuffer,sequenceBarrier,workHandlers[i],exceptionHandler,workSequence);
}
}
当调用handleEventsWithWorkerPool或者handleEventsWithWorkerPool绑定事件处理器时,内部会调用 ringBuffer.newBarrier(barrierSequences)生成SequenceBarrier,SequenceBarrier内部会持有ringBuffer的序号器Sequence的引用,同时BatchEventProcessor或者BatchEventProcessor会持有刚才创建的SequenceBarrier引用,消费的过程中,通过SequenceBarrier来获取能消费的位置。
3.小结
来一张没那么标准的图来总结下,生产者和消费者通过共同维护ringbuffer的里的seq变量,从而分享生产和消费进度,同时,借助“序号屏障”来保证,消费的速度,不能超过生产的速度。
二、序号详解
那么,Sequence内部到底是如何上述功能的,不整虚的,先来点干货。
以上就是Sequencer的类图,往上看,继承了Cursored, Sequenced,分别定义了一些获取序号的方法;往下看,最终的实现类有两个,分别是SingleProducerSequencer和MultiProducerSequencer,区别在于一个是给单生产者用,一个是给多生产者用;同时AbstractSequencer持有Sequence的引用,也就是说Sequence才是整个Sequencer的核心
1.Sequence源码
Sequence可以按照AtomicLong来理解,内部借助UNSAFE类操作long变量,但是更加高效,通过允填充,消除了CPU伪共享问题
class LhsPadding
{
protected long p1, p2, p3, p4, p5, p6, p7;
}
class Value extends LhsPadding
{
protected volatile long value;
}
class RhsPadding extends Value
{
protected long p9, p10, p11, p12, p13, p14, p15;
}
public class Sequence extends RhsPadding
{
static final long INITIAL_VALUE = -1L;
private static final Unsafe UNSAFE;
private static final long VALUE_OFFSET;
static
{
UNSAFE = Util.getUnsafe();
try
{
VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
}
catch (final Exception e)
{
throw new RuntimeException(e);
}
}
public Sequence()
{
this(INITIAL_VALUE);
}
public Sequence(final long initialValue)
{
UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue);
}
public long get()
{
return value;
}
public void set(final long value)
{
UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);
}
public void setVolatile(final long value)
{
UNSAFE.putLongVolatile(this, VALUE_OFFSET, value);
}
public boolean compareAndSet(final long expectedValue, final long newValue)
{
return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);
}
public long incrementAndGet()
{
return addAndGet(1L);
}
public long addAndGet(final long increment)
{
long currentValue;
long newValue;
do
{
currentValue = get();
newValue = currentValue + increment;
}
while (!compareAndSet(currentValue, newValue));
return newValue;
}
@Override
public String toString()
{
return Long.toString(get());
}
}
2.AbstractSequencer分析
看完序号的核心逻辑,再来分析下,生产者获取序号和发布序号的具体实现,由于生产者分为两类,分别是单线程生产者和多线程生产者,对应两种序号管理机制,下面分析源码的时候,会同时罗列各自序号管理的实现,以便大家可以轻松的进行比对分析。
- tryNext -> 获取下一个可用的生产序号
// MultiProducerSequencer实现
public long tryNext(int n) throws InsufficientCapacityException
{
if (n < 1)
{
throw new IllegalArgumentException("n must be > 0");
}
long current;
long next;
do
{
current = cursor.get();
next = current + n;
// 容量判断
if (!hasAvailableCapacity(gatingSequences, n, current))
{
throw InsufficientCapacityException.INSTANCE;
}
}
// case
while (!cursor.compareAndSet(current, next));
return next;
}
// SingleProducerSequencerPad实现
@Override
public long tryNext(int n) throws InsufficientCapacityException
{
if (n < 1)
{
throw new IllegalArgumentException("n must be > 0");
}
// 容量判断
if (!hasAvailableCapacity(n))
{
throw InsufficientCapacityException.INSTANCE;
}
// 简单粗暴
long nextSequence = this.nextValue += n;
return nextSequence;
}
通过对别可以发现,MultiProducerSequencer的tryNext方法内部通过case操作Sequence cursor变量,实现多线程生产的安全保障;SingleProducerSequencerPad实现就相对简单,有容量即可正常获取,毕竟单线程嘛;
- hasAvailableCapacity --> 查看当前RingBuffer是否能写入
// MultiProducerSequencer实现
private boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity, long cursorValue)
{
// 下一位置加上所需容量减去整个bufferSize,如果为正数,那证明至少转了一圈,则需要检查gatingSequences(由消费者更新里面的Sequence值)以保证不覆盖还未被消费的
//由于最多只能生产不大于整个bufferSize的Events。所以减去一个bufferSize与最小sequence相比较即可
long wrapPoint = (cursorValue + requiredCapacity) - bufferSize;
// 当前消费者,消费的最小位置
long cachedGatingSequence = gatingSequenceCache.get();
//如果wrapPoint > cachedGatingSequenc说明生产者追上消费者
//cachedGatingSequence(消费的最小进度) > cursorValue(当前写入的位置),绕了一圈了
//因为正常来说,生产者先写,消费者后消费,cachedGatingSequence应该小于cursorValue,你只能看到我的车尾灯
if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue)
{
// 重新获取下消费进度最小值
long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue);
// 更新最小值
gatingSequenceCache.set(minSequence);
// 如果写入位置依然大于最小值,则不能写入
if (wrapPoint > minSequence)
{
return false;
}
}
return true;
}
// SingleProducerSequencer判断
public boolean hasAvailableCapacity(final int requiredCapacity)
{
// 下一个生产Sequence位置
long nextValue = this.nextValue;
// 下一位置加上所需容量减去整个bufferSize,如果为正数,那证明至少转了一圈
long wrapPoint = (nextValue + requiredCapacity) - bufferSize;
// 当前消费者消费的位置,消费者更新这个值
long cachedGatingSequence = this.cachedValue;
//如果wrapPoint > cachedGatingSequenc说明生产者追上消费者
//如果cachedGatingSequence > nextValue 说明已经绕了一圈
if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
{
cursor.setVolatile(nextValue); // StoreLoad fence
long minSequence = Util.getMinimumSequence(gatingSequences, nextValue);
this.cachedValue = minSequence;
if (wrapPoint > minSequence)
{
return false;
}
}
return true;
}
二者的区别在于,MultiProducerSequencer.hasAvailableCapacity判断能否获取到序号n,是通过case操作生成的。而SingleProducerSequencer.hasAvailableCapacity则直接使用nextValue变量即可,没有多线程安全问题。另外判断容量是否可用的时候,都用到了“gatingSequences”变量,这个变量实际创建消费者的时候,会持有消费者的消费进度的引用,所以可以通过这个数组获取到当前ringbuffer的所有消费者的消费进度,那么在生产者生产的时候,保证不超过消费者组里最小的那个进度即可。
- publish -> 发布序号
// MultiProducerSequencer 实现
@Override
public void publish(final long sequence)
{
setAvailable(sequence);
waitStrategy.signalAllWhenBlocking();
}
// SingleProducerSequencer
@Override
public void publish(long sequence)
{
cursor.set(sequence);
waitStrategy.signalAllWhenBlocking();
}
SingleProducerSequencer的实现比较简单,因为是单线程生产,直接更新游标即可,同时唤醒等待的消费者(猜猜是在哪等待的呢,不要急,后面依然会有相关的分析~);MultiProducerSequencer稍微复杂点,其内部其实维护了 int[] availableBuffer变量,用来记录ringBuffer的每个槽的状态,毕竟是多线程线写。
private void setAvailable(final long sequence)
{
setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence));
}
通过calculateIndex计算出当前的sequence在availableBuffer数组中的位置,从而决定自己要占用哪个槽; calculateAvailabilityFlag则用来计算圈数,但是也就是一个标志位,暂时没看到其他作用。
private void setAvailableBufferValue(int index, int flag)
{
long bufferAddress = (index * SCALE) + BASE;
UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag);
}
通过调用UNSAFE.putOrderedInt方法,加写屏障,功能上等价于this.availableBuffer[index] = flag,保证了对publish发布的event事件对象的更新一定先于对availableBuffer对应下标值的更新, 避免消费者拿到新的发布序列号时由于新event事件未对其可见,而错误的消费了之前老的event事件。
三、小结
本篇主要介绍了序号器在生产者端是如何更新和发布的,由于Disruptor支持两种生产模式,单线程和多线程,底层分别提供了各自的序号管理器,分析代码的时候可以比对一起看看差别。消费者需要结合“序号屏障”一起来来看,放到下篇文章详细分析~