【跟着中间件学技术:disruptor 源码学习】

disruptor 源码学习


前言

源码gitee仓库disruptor

之前因为在工作上做优化,在保证业务顺序的语义下使用disruptor把业务链路RT从平均400ms优化到40ms,感慨disruptor 内存队列的强大,所以很好奇disruptor为什么这么优秀,于是利用国庆假期时间来学习研究一下disruptor框架的源码,该系列文章主要分享一下自己学习的心得以及思考!

在分析disruptor源码之前,简单给大家贴几张图,简单介绍一下基础的生产消费者模型以及juc的生产消费者模型和LinkedBlockingQueue,大家可以对比着学习一下,这样就可以比较清晰的了解disruptor
生产消费者模型:
请添加图片描述

ArrayBlockingQueue模型请添加图片描述

LinkedBlockingQueue模型:
请添加图片描述

一、生产者

生产者这块的代码,比较简单,就没有画图了梳理了,后面消费者那块的源码画了图的。

在介绍生产者源码之前,我们先看一下,生产者实例初始化时从哪里开始的

(1)Disruptor 构造函数

        Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(new LongEventFactory(), bufferSize,
                executor,
                ProducerType.SINGLE,  //多个生产者
                new YieldingWaitStrategy());

(2)枚举获取生产者实列

    public static <E> RingBuffer<E> create(
            final ProducerType producerType,
            final EventFactory<E> factory,
            final int bufferSize,
            final WaitStrategy waitStrategy)
    {
        switch (producerType)
        {
            case SINGLE:
                return createSingleProducer(factory, bufferSize, waitStrategy);
            case MULTI:
                return createMultiProducer(factory, bufferSize, waitStrategy);
            default:
                throw new IllegalStateException(producerType.toString());
        }
    }

所以在初始化的时候,根据ProducerType的枚举类型去决定初始化 单生产者 还是多生产者

(3) 单生产者:

    public static <E> RingBuffer<E> createSingleProducer(
            final EventFactory<E> factory,
            final int bufferSize,
            final WaitStrategy waitStrategy)
    {
        SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy);
        return new RingBuffer<>(factory, sequencer);
    }

(4) 多生产者:

    public static <E> RingBuffer<E> createMultiProducer(
            final EventFactory<E> factory,
            final int bufferSize,
            final WaitStrategy waitStrategy)
    {
        MultiProducerSequencer sequencer = new MultiProducerSequencer(bufferSize, waitStrategy);

        return new RingBuffer<>(factory, sequencer);
    }

很明显不管初始化何种类型生产者,生产者实例都会作为构造参数之一放入 存储模型RingBuffer的构造函数,关于RingBuffer后面第三节再分析,到这里,我们已经找到生产者的入口了。

简单小结一下:

  • 生产者的类型由Disruptor构造函数传入的ProducerType枚举类型决定
  • 生产者初始化后会成为 存储模型RingBuffer的属性之一
  • ProducerType 目前只支持2种:SingleProducerSequencer、MultiProducerSequencer
1.1 单生产者:SingleProducerSequencer

相信到这里,大家应该清楚生产者初始化的入口了,目前源码都比较简单

接下来我们就开始具体剖析生产者这个角色是如何提供生产的能力的、如何设计的以及这样设计的好处!

(1)由于SingleProducerSequencer的父父父类很多,我这里简单把SingleProducerSequencer实列拥有的属性列在一起

//    SingleProducerSequencer 
	// 56个字节
	protected byte
            p10, p11, p12, p13, p14, p15, p16, p17,
            p20, p21, p22, p23, p24, p25, p26, p27,
            p30, p31, p32, p33, p34, p35, p36, p37,
            p40, p41, p42, p43, p44, p45, p46, p47,
            p50, p51, p52, p53, p54, p55, p56, p57,
            p60, p61, p62, p63, p64, p65, p66, p67,
            p70, p71, p72, p73, p74, p75, p76, p77;
//  SingleProducerSequencerFields
    // 生产者当前生产的最新序号  初始值 -1
	// 
    long nextValue = Sequence.INITIAL_VALUE;

    // 拿到上一次的GatingSequence(cachedValue 存储的是上一次多个消费者消费最小的序号)
    long cachedValue = Sequence.INITIAL_VALUE;
//  SingleProducerSequencerPad
	// 56个字节
    protected byte
            p10, p11, p12, p13, p14, p15, p16, p17,
            p20, p21, p22, p23, p24, p25, p26, p27,
            p30, p31, p32, p33, p34, p35, p36, p37,
            p40, p41, p42, p43, p44, p45, p46, p47,
            p50, p51, p52, p53, p54, p55, p56, p57,
            p60, p61, p62, p63, p64, p65, p66, p67,
            p70, p71, p72, p73, p74, p75, p76, p77;

//  AbstractSequencer
    private static final AtomicReferenceFieldUpdater<AbstractSequencer, Sequence[]> SEQUENCE_UPDATER =
        AtomicReferenceFieldUpdater.newUpdater(AbstractSequencer.class, Sequence[].class, "gatingSequences");
	// 队列大小
    protected final int bufferSize;
	// 等待策略
    protected final WaitStrategy waitStrategy;
    // cursor 相当于 自己的 event id, 存储的是自己的 发布过的序号
    protected final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);
	// 1级消费者门禁序列集合
    protected volatile Sequence[] gatingSequences = new Sequence[0];

这里简单说一下生产者几个比较重要的属性

  • cursor:它负责生产序号生成
  • gatingSequences:1级消费者门禁序号集合,生产者生产时序号cursor不能超过 gatingSequences集合最小值,保证生产安全

除此之外大家会发现总共有112个额外的站位字节属性,大家可以看一下内存布局

请添加图片描述

网上有文章说,这是disruptor的技巧之一 cpu缓存行破除伪共享,但是笔者没有太明白生产者这里为什么这里要破除伪共享(大家可以在评论区打下自己的思考),关于笔者的思考放在了文章末尾

(2)接下来我们就看一下SingleProducerSequencer是如何从RingBuffer获取生产序号的

首先我们还是看一下入口:

        long sequence = ringBuffer.next();

入口就是生产者的模版代码:通过ringBuffer实列获取生产序号

    public long next()
    {
        return sequencer.next();
    }

这里的sequencer实列其实就是前面在剖析Disruptor构造函数的时候说过的,生产者初始化后会将自己的实例传给ringBuffer构造函数,所以本质上生产序号的管理还是由生产者来实现的,因为这里分析的是SingleProducerSequencer,

所以我们直接来看他的代码实现:

    public long next(final int n)
    {
        if (n < 1 || n > bufferSize)
        {
            throw new IllegalArgumentException("n must be > 0 and < bufferSize");
        }
        // 总是拿到生产者已生产的当前序号
        long nextValue = this.nextValue;
        //生产者当前序号值+期望获取的序号数量后, 达到的序号值
        long nextSequence = nextValue + n;
        //减掉RingBuffer的总的 bufferSize 值,用于判断是否出现‘覆盖’, wrapPoint 为  nextSequence 减去一个环的大小

        //缠绕点
        long wrapPoint = nextSequence - bufferSize;
        // 拿到上一次的GatingSequence,因为是缓存,这里不是最新的
        long cachedGatingSequence = this.cachedValue;

        // cachedValue就是缓存的消费者中最小序号值,为啥是cache呢? 他仅仅是缓存,并不是当前最新的‘消费者中最小序号值’,
        // cachedValue是上次程序进入到下面的if判定代码段时,被赋值的当时的‘消费者中最小序号值’
        // 这样做的好处在于:在判定是否出现覆盖的时候,不用每次都调用 计算‘消费者中的最小序号值’,从而节约开销。
        // 只要确保当生产者的 序号,大于了缓存的 cachedGatingSequence 一个bufferSize时,重新获取一下 getMinimumSequence()即可。

        // 需要重新获取cachedGatingSequence 的两个条件:
        //1 (wrapPoint > cachedGatingSequence) :
        //  当生产者已经超过上一次缓存的‘消费者中最小序号值’(cachedGatingSequence)一个‘Ring’大小(bufferSize)的时候
        //  此条件,避免当生产者一直在生产,但是消费者不再消费的情况下,出现‘覆盖’
        //2  (cachedGatingSequence > nextValue) :消费者seq> 生产者seq , 已经消费完了
        //  理论上:生产者和消费者均为顺序递增的,且生产者的seq“先于”消费者的seq,起一个边界作用

        if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
        {
            cursor.setVolatile(nextValue);  // StoreLoad fence

            long minSequence;
            // 自旋等待, 其中 gatingSequences 是‘消费者中最小序号值’
            // 等待消费者,消费一些事件,ringbuffer 有空闲空间
            while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue)))
            {
                LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin?
            }

            this.cachedValue = minSequence;
        }
        // 将获取的nextSequence赋值给生产者当前值nextValue
        this.nextValue = nextSequence;

        return nextSequence;
    }

这里代码不长,但是理解起来还是有点绕,关键点在于几个变量的理解

  • nextValue:生产者已生产的当前序号
  • nextSequence :生产者当前生产的序号值+期望获取的序号
  • wrapPoint :nextSequence - bufferSize
  • cachedGatingSequence :1级消费者最小门禁序号

wrapPoint > cachedGatingSequence ==> nextSequence > bufferSize + cachedGatingSequence

nextSequence  : 代表想要生产达到的序号
bufferSize + cachedGatingSequence :代表1级消费者最慢消费的序号
所以  wrapPoint > cachedGatingSequence判断 其实就是想控制:生产者的速度不能超过最慢消费者的速度,避免造成数据覆盖

如果期望生产的序号超过了最慢消费者消费的序号,即wrapPoint > cachedGatingSequence,那么就进行自旋,等待消费者消费,直到有额外的空间可以进行生产

所以简单小结一下:

1.1.1 SingleProducerSequencer是如何获取生产序号的
  • 通过申请生产序号 和 门禁序号来控制 生产者是否可能出现覆盖
  • 如果可能出现覆盖,那么生产者线程在next()方法中就会进行自旋,直到不会出现覆盖情况,申请成功

上面步骤只是生产者申请生产序号的过程,其实生产者申请后,会由ringBuffer获取到实际的包装存储对象,往包装对象里面塞完我们的业务数据之后,生产者还会进行最后的环节生产发布通知

接下来,我们就看一下生产的最后环境:生产发布通知

首先,我们还是看一下入口:

            ringBuffer.publish(sequence);

生产发布通知主要在 生产模版代码: ringBuffer.publish(sequence) 处

    public void publish(final long sequence)
    {
        sequencer.publish(sequence);
    }

这里的sequencer其实就是生产者实列,前面在分析生产序号申请next()方法的时候已经解释过了

所以我们直接进入publish()源码:

SingleProducerSequencer#publish()

    public void publish(final long sequence)
    {
        // 【1】给生产者cursor游标赋值新的生产序号,说明该sequenc对应的对象数据已经填充(生产)完毕
        cursor.set(sequence);// 这个cursor即生产者生产时移动的游标,是AbstractSequencer的成员变量
        // 【2】根据阻塞策略将所有消费者唤醒
        waitStrategy.signalAllWhenBlocking();
    }

(1)以有序的方式更新生产者生产下标

    public void set(final long value)
    {
        // 有序 写入 value属性值
        UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);
    }

(2)通过waitStrategy实例唤醒所有被阻塞的消费者

这里就以默认实现为例BlockingWaitStrategy

  • BlockingWaitStrategy 属性
    private final Lock lock = new ReentrantLock();
    private final Condition processorNotifyCondition = lock.newCondition();
  • 唤醒方法:BlockingWaitStrategy 实现依赖ReentrantLock锁和条件队列实现 生产者向消费者通信的方式
    /**
     * 如果生产者新生产一个元素,那么唤醒所有消费者
     */
    @Override
    public void signalAllWhenBlocking()
    {
        lock.lock();
        try
        {
            processorNotifyCondition.signalAll();
        }
        finally
        {
            lock.unlock();
        }
    }

所以这里简单小结一下:

1.1.2 SingleProducerSequencer如何进行生产通知发布的
  • 通过有序的方式修改cursor实列的value值
  • 依赖WaitStrategy具体实现类来实现消费者的通知,而BlockingWaitStrategy 实现是依赖ReentrantLock锁和条件队列实现 生产者向消费者通信的方式

关于WaitStrategy其他的实现类的方式,大家可以看源码,当然大家也可以实现WaitStrategy接口进行扩展自定义实现通知消费者的方式

1.2 多生产者:MultiProducerSequencer

接下来,我们趁热打铁,把MultiProducerSequencer如何获取生产序号也分析了

关于MultiProducerSequencer 获取生产序号的入口跟 SingleProducerSequencer 一样,我们直接分析next方法

    public long next(int n)
    {
        if (n < 1)
        {
            throw new IllegalArgumentException("n must be > 0");
        }

        long current;
        long next;

        do 
        {
            // 已经生产的序号
            current = cursor.get(); // 生产者当前生产的序号
            // 期望申请的生产序号
            next = current + n;
            // 缠绕点
            long wrapPoint = next - bufferSize;
            // 消费者消费的最小序号
            long cachedGatingSequence = gatingSequenceCache.get();

            if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current)
            {
                // 生产 可能比 消费快了
                long gatingSequence = Util.getMinimumSequence(gatingSequences, current);

                if (wrapPoint > gatingSequence)
                {
                    LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy?
                    continue;
                }

                gatingSequenceCache.set(gatingSequence);
            }
            else if (cursor.compareAndSet(current, next))
            {
                // 可以生产
                // 通过cas 来尝试更新生产者最新的生产序号
                // 更新成功   跳出 do while()
                // 更新失败   说明有其他生产者竞争,继续自旋
                // 相较于单生产者,多了一步cas更新cursor
                break;
            }
        }
        while (true);

        return next;
    }

整体核心逻辑跟SingleProducerSequencer差别不大,主要在于:MultiProducerSequencer里加入了 cas自旋无锁处理(主要处理共享变量cursor实例volatile修饰的value值),这里其实就是很好的并发处理实操技巧参考(cas)

所以简单小结一下:

1.2.1 MultiProducerSequencer是如何获取生产序号的
  • 通过cas自旋无锁化处理多生产者修改cursor属性实列volatile修饰的value值
  • 其他生产者 速度是否可能超过 最慢消费者 逻辑 跟 SingleProducerSequencer类似

相信到这里大家会发现cursor变量和门禁变量(gatingSequenceCache)在生产者中起着非常重要的作用

1.2.2 MultiProducerSequencer如何进行生产通知发布的

大体多生产者跟但单生产者类似,但是在处理生产游标上有一些区别

二、消费者

大家如果了解消费者生产者的基本模型,那么应该清楚消费者都应该具备以下3种基础能力:

  • 持续消费能力(持续能力)
  • 消费者之间安全的竞争能力(线程安全能力)
  • 无物可消费时怎么办能力(后勤能力)

disruptor本身也是一个生产、消费者模型,所以disruptor的消费者也具备以上3种基本的能力,除此之外,disruptor还提供了多级消费者之间的依赖管理能力,即(消费者a 做完a事情后,消费者b 才能做 b事情,那么可以说 消费者b 依赖消费者a),或者说消费者编排能力,这个能力有什么好处呢?可以帮我们完成更复杂的业务处理逻辑

关于disruptor消费者源码的研究逻辑主要如下:首先先看一下disruptor是如何管理(或者说构建)多级消费者之间的依赖关系,其次我们再看一下disruptor在实现生产消费者模型的3种基础能力有不有什么可以学习借鉴的精华之处

2.1 消费者之间的协作模式:disruptor如何管理多级消费者之间的依赖关系

在分析源码之前,我先给大家看一个2级消费者的较完整的依赖图(笔者这里的2级指是有2层消费者)

请添加图片描述

图中分别有3个消费者:消费者 a1、a2 和 b ,其中a1、a2属于并行的关系,而b和a1、a2属于依赖关系(即a1 或者 a2 消费完之后 b 才能继续消费),相信大家看到这里可能会蒙圈(没看过源码确实会蒙,但是看过之后就会觉得这个图很清晰)

为了避免上面图的复杂性,笔者画了一个消费者依赖关系简图:主要解释消费者a1、a2、b之间的依赖关系

请添加图片描述

以上消费者构建的代码demo (部分)

        EventHandler consumerA1 = new CoreReadHandler("consumer a1");
        EventHandler consumerA2 = new CoreReadHandler("consumer a2");
        EventHandler consumerB = new CoreReadHandler("consumer b");
        // 构建消费者 a1 a2
        EventHandlerGroup firstGroup = disruptor.handleEventsWith(consumerA1, consumerA2);
        // 在 消费者a1 a2基础之上构建 b
        firstGroup.handleEventsWith(consumerB);

其实这个demo代码就是构建消费者依赖关系的入口,接下来就从handleEventsWith方法开始源码剖析

Disruptor#handleEventsWith()

    public final EventHandlerGroup<T> handleEventsWith(final EventHandler<? super T>... handlers)
    {
      // 注意 这里传入了一个new Sequence[0]  空数组
        return createEventProcessors(new Sequence[0], handlers);
    }

Disruptor#createEventProcessors()

    EventHandlerGroup<T> createEventProcessors(
        final Sequence[] barrierSequences, // 消费者1
        final EventHandler<? super T>[] eventHandlers)
    {
        checkNotStarted();

        // (1)根据传进来的处理器实例长度构造了一个Sequence数组processorSequences   这里面放着是 eventHandlers对应下标的 序列号值
        final Sequence[] processorSequences = new Sequence[eventHandlers.length];
        
        //(2)SequenceBarrier实列
        // * 1)waitFor 获取下一个可用的消费序号
        // * 2)getCursor  获取依赖对象(生产者 或者 消费者序号)序号的最小序号
        final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);
        //(3)包装eventHandlers处理器为BatchEventProcessor,同时塞了 ringBuffer实列、barrier实列以及eventHandler
        for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
        {
            final EventHandler<? super T> eventHandler = eventHandlers[i];

            // step3 : 构建 消费者处理器
            // 有多少个eventHandlers就创建多少个BatchEventProcessor实例(消费者),
            // 但需要注意的是同一批次的每个BatchEventProcessor实例共用同一个SequenceBarrier实例
            final BatchEventProcessor<T> batchEventProcessor =
                new BatchEventProcessor<>(ringBuffer, barrier, eventHandler);

            if (exceptionHandler != null)
            {
                // 如果设置了exceptionHandler
                batchEventProcessor.setExceptionHandler(exceptionHandler);
            }
            // 将batchEventProcessor, eventHandler, barrier封装成EventProcessorInfo实例并加入到ConsumerRepository相关集合
            // ConsumerRepository作用:提供存储机制关联EventHandlers和EventProcessors
            //(4) 把包装batchEventProcessor实列添加到了consumerRepository里面,同时还放了eventHandler和barrier
            consumerRepository.add(batchEventProcessor, eventHandler, barrier);  // 如果构建执行顺序链比如A->B,那么B消费者也一样会加入consumerRepository的相关集合
            // 获取到每个消费的消费sequece并赋值给processorSequences数组
            // 即processorSequences[i]引用了BatchEventProcessor的sequence实例,
            // 但processorSequences[i]又是构建生产者gatingSequence和消费者执行器链dependentSequence的来源
            processorSequences[i] = batchEventProcessor.getSequence();
        }
        // 每次添加完事件处理器后,更新门禁序列,以便后续调用链的添加  barrierSequences:依赖的序号 引用
        // 所谓门禁,是指后续消费链的消费,不能超过前边
        // 第一次 barrierSequences是空数组
        // (5)updateGatingSequencesForNextInChain方法作用: (1)更新生产者门禁集合 (2)标记barrierSequences不是消费链的尾结点
        updateGatingSequencesForNextInChain(barrierSequences, processorSequences);
        // (6)最终返回封装了Disruptor、ConsumerRepository和消费者sequence数组processorSequences的EventHandlerGroup对象实例返回
        return new EventHandlerGroup<>(this, consumerRepository, processorSequences);
    }

这个方法的核心主要做了6件事情,分别对应代码中的(1)-(6)

接下来主要重点分析一下消费者依赖管理的源码部分

2.1.1 获取SequenceBarrier实列 代码注释(2)
        //(2) SequenceBarrier实列
        // * 1)waitFor 获取下一个可用的消费序号
        // * 2)getCursor  获取依赖对象(生产者 或者 消费者序号)序号的最小序号
        final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);

Sequencer#newBarrier(Sequence…)

    public SequenceBarrier newBarrier(Sequence... sequencesToTrack)
    {  // SequenceBarrier实现类ProcessingSequenceBarrier
        return new ProcessingSequenceBarrier(this, waitStrategy, cursor, sequencesToTrack);
    }

ProcessingSequenceBarrier是一个比较核心的类,我们接下来看一下他的属性以及构造方法

  • 属性
    private final WaitStrategy waitStrategy; //等待策略waitStrategy

    private final Sequence dependentSequence;   // 依赖序号  不能超过  ,
    private volatile boolean alerted = false; // 警报
    private final Sequence cursorSequence;  //生产者位cursorSequence
    private final Sequencer sequencer; // 生产者Sequencer
  • 构造函数
    ProcessingSequenceBarrier(
            final Sequencer sequencer, // 生产者实列引用
            final WaitStrategy waitStrategy, // 等待策略
            final Sequence cursorSequence, // 生产者 当前生产的序号 序号
            final Sequence[] dependentSequences // 依赖的序号   注意:第一次的时候,dependentSequences是一个空数组
    ) {
        this.sequencer = sequencer; // 生产者实列
        this.waitStrategy = waitStrategy; // 等待策略waitStrategy
        // cursorSequence其实持有的是AbstractSequencer的成员变量cursor实例的引用
        this.cursorSequence = cursorSequence; // 当前生产者序号
        // 如果消费者前面依赖的其他消费者链长度为0,其实就是只有一个消费者的情况下,
        // 那么dependentSequence其实就是生产者的cursor游标
        if (0 == dependentSequences.length) {
            // dependentSequences 为0 那么依赖 生产者序号
            dependentSequence = cursorSequence;
        }
        // 如果消费者前面还有被依赖的消费者,那么dependentSequence就是前面消费者的sequence
        // 如果是指定执行顺序链的会执行到这里,
        else {// FixedSequenceGroup 包装了Sequence[] dependentSequences数组,然后提供了获取这个数组中最小序号的方法
            dependentSequence = new FixedSequenceGroup(dependentSequences);
        }
    }

注意代码 13行,有一个0 == dependentSequences.length判断,如果传过来的dependentSequences长度为0,那么dependentSequence属性就会被赋值为传过来的cursorSequence,大家如果身边有源码的话,可以看一看Sequencer#newBarrier(Sequence…)方法传的参数具体含义,这里直接跟大家说结果:

(1)dependentSequences 为空,表示当前消费者为1级消费者,直接依赖生产者不依赖其他消费者,比如上面的消费者依赖图中的消费者a1,a2

(2)dependentSequences 不为空,表示当前消费者非1级消费者,即依赖上一级消费者,比如上面的消费者依赖图中的消费者b, 依赖消费者a1和a2

(3) FixedSequenceGroup对象的get方法提供了获取dependentSequences中最小序号的实现

  • 2个重要方法

(1)waitFor(final long sequence) :获取消费序号

这个方法后面分析消费者具体消费时再说

(2)getCursor() :获取当前依赖屏障最小序号

    public long getCursor() {
        return dependentSequence.get();
    }

这个方法主要依赖属性dependentSequence的get方法实现,而dependentSequence属性,我们其实已经在ProcessingSequenceBarrier的构造函数中提到,即FixedSequenceGroup对象实列(这个比较简单)

2.1.2 包装EventHandler为BatchEventProcessor 代码注释(3)
final BatchEventProcessor<T> batchEventProcessor =new BatchEventProcessor<>(ringBuffer, barrier, eventHandler);

将EventHandler包装为BatchEventProcessor类,比较关键的是,构造BatchEventProcessor传如了barrier,这个barrier就是我们2.1.1 分析的SequenceBarrier实列,关于BatchEventProcessor的源码分析,我们放在后面再分析,目前大概知道干了这么一件事情即可

2.1.3 包装EventHandler为BatchEventProcessor 代码注释(4)
            // 将batchEventProcessor, eventHandler, barrier封装成EventProcessorInfo实例并加入到ConsumerRepository相关集合
            // ConsumerRepository作用:提供存储机制关联EventHandlers和EventProcessors
            //(4) 把包装batchEventProcessor实列添加到了consumerRepository里面,同时还放了eventHandler和barrier
            consumerRepository.add(batchEventProcessor, eventHandler, barrier);

代码注释(4)主要把 EventHandler的包装对象batchEventProcessor实列存起来,源码比较简单,就是放在2个map里存起来

2.1.4 更新生产者的门禁序列 代码注释(5)
        // 每次添加完事件处理器后,更新门禁序列,以便后续调用链的添加  barrierSequences:依赖的序号 引用
        // 所谓门禁,是指后续消费链的消费,不能超过前边
        // 第一次 barrierSequences是空数组
        // (5) updateGatingSequencesForNextInChain方法作用: (1)更新生产者门禁集合 (2)标记barrierSequences不是消费链的尾结点
        updateGatingSequencesForNextInChain(barrierSequences, processorSequences);

笔者最开始看这个方法的时候没太懂,直到把传入的参数barrierSequences弄懂之后才比较清晰的

趴一下源码

    // barrierSequences : 依赖的屏障序号
    private void updateGatingSequencesForNextInChain(final Sequence[] barrierSequences, final Sequence[] processorSequences)
    {
        if (processorSequences.length > 0)
        {
            // 把当前构造的消费者序号放到 生产者的门禁集合里
            ringBuffer.addGatingSequences(processorSequences);// processorSequences作为门禁放入  生产者的门禁集合
            for (final Sequence barrierSequence : barrierSequences)
            {
                // 把当前消费者的依赖对象序号从 生产者的门禁集合里移除
                // 为什么要这样做呢?  因为 消费者肯定慢于 依赖的消费者序号(屏障),所以构造的时候就把屏障序号从生产者门禁集合中移除,这样在求生产者门禁集合最小序号的时候就会更快
                ringBuffer.removeGatingSequence(barrierSequence);
            }
          // 标记当前消费者是不是消费链的尾结点
            consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences);
        }
    }

理解updateGatingSequencesForNextInChain方法的核心主要在于理解 barrierSequences 和 processorSequences参数的含义

processorSequences :这个Sequence数组主要存的是 代码注释(3)处的包装对象BatchEventProcessor 的Sequence属性,其实就是每个消费者的消费序号

barrierSequences :主要指当前消费者的依赖对象的序号(依赖对象可能是 消费者,也可能是 生产者)

这个方法主要做了3件事:

  • 第一件事:把当前构造的消费者序号放到 生产者的门禁集合里
  • 第二件事:把当前消费者的依赖对象序号从 生产者的门禁集合里移除
  • 第三件事:标记当前消费者是不是消费链的尾结点

关于第二件事,大家第一次看到这里,可能跟笔者有一样的疑惑,为什么要把当前消费者的依赖对象序号从 生产者的门禁集合里移除呢?

因为 消费者肯定慢于 依赖的消费者序号(屏障),所以构造的时候就把屏障序号从生产者门禁集合中移除,这样在求生产者门禁集合最小序号的时候就会更快

这里的 ringBuffer.addGatingSequences(processorSequences) 和 ringBuffer.removeGatingSequence(barrierSequence)方法最终调用的是SequenceGroups对象的addSequences和removeSequence方法,这两个方法大家都可以看一下:

disruptor主要通过:cas无锁自旋 + 读写分离的方式 来对生产者门禁集合元素进行添加和删除操作

2.1.5 返回包装对象 EventHandlerGroup 实列-方便消费者序号依赖传递 代码注释(6)
        // (6) 最终返回封装了Disruptor、ConsumerRepository和消费者sequence数组processorSequences的EventHandlerGroup对象实例返回
        return new EventHandlerGroup<>(this, consumerRepository, processorSequences);

其实每一层消费者都对应一个EventHandlerGroup实例,比如上面提到的消费者a1、a2即为同一层消费者,b为另一层消费者,笔者认为这样的好处在于方便代码层面直观的构建消费者链,比如 a1,a2 -> b(有点链式调用的意思)

以上大概就是disruptor消费者之间的协作模式的源码剖析

简单小结一下

  • 获取SequenceBarrier实列,SequenceBarrier提供屏障能力(消费者依赖关系的核心)
  • 包装 消费者处理器EventHandler 为 BatchEventProcessor 实列
  • 更新生产者门禁集合
  • 将同一层消费者封装为一个EventHandlerGroup实例,方便下一级消费者依赖传递(消费者依赖关系的核心)

优秀的地方:

  • cas自旋无锁 + 读写分离 维护门禁集合
2.2 BatchEventProcessor批处理消费者

关于BatchEventProcessor的话,相信大家能够看到这里已经对它有一点印象了,接下来就具体分析BatchEventProcessor是如何进行消费的

首先,我们还是看一下代码的入口位置:

        disruptor.start();

Disruptor#start()

    public RingBuffer<T> start()
    {
        checkOnlyStartedOnce();
        // 遍历每一个BatchEventProcessor消费者(线程)实例,并把该消费者线程实例跑起来
        for (final ConsumerInfo consumerInfo : consumerRepository)
        {	
            consumerInfo.start(executor);
        }
        return ringBuffer;
    }
    public void start(final Executor executor)
    {
        executor.execute(eventprocessor);
    }

这里的consumerInfo实列其实就是对 2.1.2 提到的BatchEventProcessor 再包装,大家可以回到 2.1.2看一看相关的代码,比较简单。这里有一点没提到的就是 BatchEventProcessor 实现了 Runnable接口

2.2.1 BatchEventProcessor 如何具体进行消费

相信有源码阅读经验的小伙伴都知道,具体逻辑肯定在BatchEventProcessor 的 run方法里

BatchEventProcessor #run()

    public void run()
    {
        if (running.compareAndSet(IDLE, RUNNING))
        {
            sequenceBarrier.clearAlert();
            notifyStart();
            try
            {
                if (running.get() == RUNNING)
                {
                    processEvents(); // 核心方法
                }
            }
            finally
            {
                notifyShutdown();
                running.set(IDLE);
            }
        }
        else
        {
            // This is a little bit of guess work.  The running state could of changed to HALTED by
            // this point.  However, Java does not have compareAndExchange which is the only way
            // to get it exactly correct.
            if (running.get() == RUNNING)
            {
                throw new IllegalStateException("Thread is already running");
            }
            else
            {
                earlyExit();
            }
        }
    }

我们直接看核心方法

BatchEventProcessor #processEvents()

    private void processEvents()
    {
        T event = null;
        // nextSequence:消费者要消费的下一个序号
        // 【重要】每一个消费者都是从0开始消费,各个消费者维护各自的sequence
        long nextSequence = sequence.get() + 1L;
        while (true)
        { // 死循环
            try
            {
                // (1)获取可以消费的序号
                final long availableSequence = sequenceBarrier.waitFor(nextSequence);
                if (batchStartAware != null)
                {
                    batchStartAware.onBatchStart(availableSequence - nextSequence + 1);
                }
                // (2)如果消费者要消费的下一个序号小于生产者的当前生产序号,那么消费者则进行消费
                while (nextSequence <= availableSequence)
                {  // 批处理
                    event = dataProvider.get(nextSequence);
                  // 具体的业务代码执行处
                    eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
                    nextSequence++;
                }
                // (3)消费完后设置当前消费者的消费进度
                // 【1】如果当前消费者是执行链的最后一个消费者,那么其sequence则是生产者的gatingSequence,因为生产者就是拿要生产的下一个sequence跟gatingSequence做比较的哈
                // 【2】如果当前消费者不是执行器链的最后一个消费者,那么其sequence作为后面消费者的dependentSequence
                sequence.set(availableSequence);
            }
          、、、 无关代码 、、、
        }
    }

这个方法很明显做了以下4件事情:

1)死循环

2)通过sequenceBarrier属性的waitFor方法获取可以消费的序号

3)如果申请序号 <= 可以消费的序号,可以批量的进行消费,直到消费序号到达可以消费的序号

4)更新当前sequence属性的消费进度

关于sequenceBarrier,我们在2.1.1部分已经有一些介绍了,接下来我们重点分析一下waitFor方法

ProcessingSequenceBarrier#waitFor()

//    sequence 申请的消费序号
public long waitFor(final long sequence)
            throws AlertException, InterruptedException, TimeoutException {
        checkAlert();
        // (1)委托给了waitStrategy的实现类
        long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this);

        if (availableSequence < sequence) {
            return availableSequence;
        }
        // 这个主要是针对多生产者的情形   单生产者返回availableSequence
        return sequencer.getHighestPublishedSequence(sequence, availableSequence);
    }

这里的WaitStrategy我们主要分析BlockingWaitStrategy源码

BlockingWaitStrategy#waitFor()

//     sequence 申请的消费序号
//     cursorSequence 生产者当前生产序号
//     dependentSequence 当前消费者的依赖对象序号
public long waitFor(long sequence, Sequence cursorSequence, Sequence dependentSequence, SequenceBarrier barrier)
            throws AlertException, InterruptedException
    {
        long availableSequence;
        // cursorSequence:生产者当前生产的序号
        if (cursorSequence.get() < sequence)
        {
            // (1)尝试申请消费的序号 > 生产者当前生产的序号
            lock.lock();
            try
            {
                while (cursorSequence.get() < sequence)
                {
                    barrier.checkAlert();
                    // 进入条件等待队列  等待被唤醒
                    processorNotifyCondition.await();
                }
            }
            finally
            {
                lock.unlock();
            }
        }
        while ((availableSequence = dependentSequence.get()) < sequence)
        {
            // (2)依赖对象的最小序号  < 尝试申请消费的序号
            barrier.checkAlert();
            ThreadHints.onSpinWait();//自旋一会
        }

        return availableSequence;
    }

这个方法主要做了2件事情

(1) 如果 尝试申请消费的序号 超过了 生产者当前生产的序号,那么进入条件等待队列,等待被唤醒

关于唤醒,前面生产者源码部分已经剖析:每次生产之后都会进行唤醒(仅BlockingWaitStrategy)

(2)如果 尝试申请消费的序号 超过了 依赖对象的序号集合最小值,那么自旋再尝试

(3) 如果以上2个条件都没进入,那么返回依赖对象的序号集合最小值

2.3 WorkProcessor 并行消费者

WorkProcessor也是消费者的一种类型,它也实现了Runnable接口相信大家跟着笔者看了BatchEventProcessor 源码,WorkProcessor大概也能猜出点啥,那么废话就不多说了,直接扒源码

WorkProcessor #run()

    public void run()
    {
        if (!running.compareAndSet(false, true))
        {
            throw new IllegalStateException("Thread is already running");
        }
        sequenceBarrier.clearAlert();

        notifyStart();

        boolean processedSequence = true;
        long cachedAvailableSequence = Long.MIN_VALUE;
        long nextSequence = sequence.get();
        T event = null;
        // 死循环
        while (true)
        {
            try
            {
                if (processedSequence)
                {
                    processedSequence = false;
                    do
                    {
                        // 这两句不是原子操作
                        nextSequence = workSequence.get() + 1L;
                        sequence.set(nextSequence - 1L);
                    }// 多消费者协作消费数据,workSequence是全局变量,存在线程安全问题,用CAS操作
                    while (!workSequence.compareAndSet(nextSequence - 1L, nextSequence));
                }
                // 如果消费者消费速度没有赶上生产者生产速度,那么进行消费
                if (cachedAvailableSequence >= nextSequence)
                {
                    event = ringBuffer.get(nextSequence);
                    workHandler.onEvent(event);
                    processedSequence = true;
                }
                // 消费者消费速度赶上生产者生产速度,那么根据WaitStrategy策略进行等待
                else
                {
                    cachedAvailableSequence = sequenceBarrier.waitFor(nextSequence);
                }
            }
              、、、无关代码、、、
            }
        }

        notifyShutdown();

        running.set(false);
    }

关于这个方法笔者就不多解释了,大家其实只要知道workSequence属性是一个全局变量,应该都明白上面的代码

简单小结一下

  • cas无锁自旋 处理全局变量 workSequence
  • 其他的逻辑跟BatchEventProcessor类似
  • 但是 WorkProcessor 消费者 没有批量消费的能力,而是每次只消费一次

亮点:

  • cachedAvailableSequence 变量:WorkProcessor 不会每一次都去调用sequenceBarrier.waitFor(),而是当自己申请消费的序号 >缓存cachedAvailableSequence 变量时才会去调用

这个优化,在高并发的场景还是有优化效果的

相信大家看到这里,再看一下之前提到的消费者依赖图,大家应该就比较清晰 disruptor 消费者的大概一个流程了

请添加图片描述

三、存储模型RingBuffer

RingBuffer 在生产消费者模式中主要担任的缓存的角色,如果你了解juc的阻塞队列,那么RingBuffer 可以简单看做是缓存队列

首先我们还是看一下分析RingBuffer 源码的入口:

RingBuffer #createSingleProducer

    public static <E> RingBuffer<E> createSingleProducer(
            final EventFactory<E> factory,
            final int bufferSize,
            final WaitStrategy waitStrategy)
    {
        SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy);
        return new RingBuffer<>(factory, sequencer);//初始化RingBuffer实列
    }

构造的时候传了2个参数:

  • sequencer:生产者的引用,前面已经分析了
  • factory:负责生成RingBuffer存储的对象实例 ,即我们重写EventFactory的实现

接下来我们就跟踪一下RingBuffer的源码

看之前呢我们还是简单看一下RingBuffer有哪些属性

3.1 RingBuffer的属性

以下属性包括RingBuffer的父、父类属性

    
 // RingBuffer
public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE;
	// 	56个字节
    protected byte
            p10, p11, p12, p13, p14, p15, p16, p17,
            p20, p21, p22, p23, p24, p25, p26, p27,
            p30, p31, p32, p33, p34, p35, p36, p37,
            p40, p41, p42, p43, p44, p45, p46, p47,
            p50, p51, p52, p53, p54, p55, p56, p57,
            p60, p61, p62, p63, p64, p65, p66, p67,
            p70, p71, p72, p73, p74, p75, p76, p77;
// RingBufferFields
    private static final int BUFFER_PAD = 32;

    private final long indexMask; // 取模用的
    private final E[] entries; // 具体存储数组 要求数组长度是 2的指数 ,
    protected final int bufferSize; // RingBuffer长度
    protected final Sequencer sequencer; // 生产者实例的引用

//  RingBufferPad
	// 56个字节
    protected byte
            p10, p11, p12, p13, p14, p15, p16, p17,
            p20, p21, p22, p23, p24, p25, p26, p27,
            p30, p31, p32, p33, p34, p35, p36, p37,
            p40, p41, p42, p43, p44, p45, p46, p47,
            p50, p51, p52, p53, p54, p55, p56, p57,
            p60, p61, p62, p63, p64, p65, p66, p67,
            p70, p71, p72, p73, p74, p75, p76, p77;

我们主要看一下核心的构造函数:

RingBufferFields

    RingBufferFields(
            final EventFactory<E> eventFactory,
            final Sequencer sequencer)
    {
        //(1) 给RingBuffer长度的属性赋值
        this.sequencer = sequencer;// 上面提到的 SingleProducerSequencer  或者多生产者 实例
        this.bufferSize = sequencer.getBufferSize(); //RingBuffer长度 

        if (bufferSize < 1)
        {
            throw new IllegalArgumentException("bufferSize must not be less than 1");
        }
        if (Integer.bitCount(bufferSize) != 1)
        {
            // 必须是2的倍数bitCount()
            throw new IllegalArgumentException("bufferSize must be a power of 2");
        }
        // 掩码 = RingBuffer长度 - 1   方便取模用
        this.indexMask = bufferSize - 1;  // 16,15   // 128, 127
        //(2)构造数组并填充
        this.entries = (E[]) new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD];
        fill(eventFactory);
    }

做了2件事,第一件给RingBuffer的属性赋值,第二件初始化并填充属性entries对象数组,不知道大家发现没,RingBuffer的长度是bufferSize,但是entries数组的长度是bufferSize + 2 * BUFFER_PAD

我们具体看一下怎么填充的:

RingBufferFields#fill()

    private void fill(final EventFactory<E> eventFactory)
    {
        for (int i = 0; i < bufferSize; i++)
        {
          // eventFactory.newInstance() 方法就是我们 在使用disruptor会用到的一个环节
            entries[BUFFER_PAD + i] = eventFactory.newInstance();
        }
    }

大家会发现entries初始化下标是从BUFFER_PAD 开始的,所以entries的存储接口可以理解为如下:
BUFFER_PAD +bufferSize +BUFFER_PAD

看到这里不知道大家有不有跟笔者一样的疑问:为什么要在数组2边添加一个BUFFER_PAD 站位呢?

感兴趣的小伙伴可以在评论区讨论!

3.2 RingBuffer的方法

申请获取生产元素下标:next()

    public long next()
    {
      // sequencer  生产者引用
        return sequencer.next();
    }

很明显申请获取生产元素下标是委托给生产者引用来实现的,具体的细节,大家可以回看生产者的源码分析部分

获取具体生产元素:get(final long sequence)

    public E get(final long sequence)
    {
        return elementAt(sequence);
    }

elementAt(sequence)

    protected final E elementAt(final long sequence)
    {
        // 通过位运算取模
        return entries[BUFFER_PAD + (int) (sequence & indexMask)];
    }

看到这里应该就很清晰了,通过sequence和indexMask相与 获取模,然后得到具体的entries数组元素

简单小结一下:RingBuffer主要具备以下能力

  • 包装对象数组entries,并提前初始化好,可以理解为简单的对象池思想
  • 对外提供提供元素获取和申请的能力,位运算获取元素

笔者疑惑的地方:

  • RingBuffer 像生产者一样,在初始话的时候都有 112个站位字节填充(为什么要这样做呢?)
  • RingBuffer的实际存储数组长度为RingBuffer的长度 + 64
四、disruptor优秀设计点总汇

到这里源码分析也差不多结束了,disruptor的整个脉络也算基本分析清楚了,相信我们以后在技术选型的时候关于disruptor也会更有底气。笔者还是建议大家,自己再独立的跟一下源码、,或者debug一下源码,可能就更清晰了。

接下来的话,简单总结一下笔者认为disruptor设计的比较好的地方:

4.1 扩展性

disruptor的几个核心的角色,比如生产者、消费者、等待策略(WaitStrategy)、业务处理器(EventHandler)都有相关的接口定义和参考,从业务的角度理解disruptor的扩展性还是很高的,比如常用单生产者、多生产者、批处理消费者、并发消费者等,最亮眼的就是设计了消费者依赖协调。

4.2 高性能

disruptor确实在性能上面很高,笔者在demo测试disruptor和juc的BlockLinkedList性能对比,disruptor要高一个8~10倍,关于disruptor性能比较好,有很多文章都可以参考,比如美团的技术博客等

笔者认为disruptor设计的性能比较高的原因有5个:

  • disruptor 基本上主要使用cas自旋无锁化思想处理并发(等待策略BlockingWaitStrategy除外,实现使用的是ReentrantLock)
  • 设计了优秀的环形缓存数据结构RingBuffer :内存池思想 + 位运算 (消费者消费O(1)的查询时间复杂度,同时也避免垃圾回收)
  • 生产者门禁序号集合高并发处理:采用读写分离 + cas
  • Sequence序号处理:采用顺序写入模式
  • Sequence通过左右填充56个字节来破处cpu缓存的伪共享问题 (这个点,源码没有分析,但是大家可以去看一下Sequence的构造,以及查资料了解一下一般操作系统的cpu缓存行大小,应该就清楚了)

前三点属于设计思想上的高性能点,后2个属于操作系统层面的高性能点

不得不说disruptor确实是一个追求低延迟、高并发的内存级别的高性能队列,上面提到的5个高性能点都可以作为我们在实际业务中设计高性能接口可以参考和应用的点

五、总结与收获

disruptor是一个典型业务属性比较强的高性能中间件(生产消费者模式业务场景),阅读完大部分disruptor的源码后,真的感慨disruptor团队追求极致的专业和code精神,同时也是通过这一次的学习,让我更加了解如何应用disruptor以及如何设计高性能的接口打下坚实的基础,笔者认为disruptor的源码难点主要在于多消费者协调那块,只要把屏障和生产者的门禁之间的关系理清楚了,就差不多了

总的来说,本次研究disruptor源码主要有3点收获:

  • 更加了解如何应用disruptor,disruptor主要应用于高并发、低延迟、内存级别的生产、消费场景
  • 通过disruptor了解到一些基础高并发知识的应用和实践,比如内存池思想 + 位运算、cas自旋无锁化思想、顺序写、读写分离 + cas等
  • 拓宽了操作系统层面的优化思路:disruptor 破除伪共享

同时也是通过这一次对于disruptor的源码阅读,让笔者更加有信心对于优秀中间件设计思想的汲取和学习

埋一个伏笔

下一次,如果有时间的话,可以跟大家分享一下:笔者是如何应用disruptor组件在不改变业务顺序的语义下将业务链路的rt从400ms优化到40ms,性能最终提高了10倍左右的经历

关于伪共享问题思考

以下是笔者目前阶段关于伪共享的思考:

笔者基于目前的认知认为Sequence类当中的112个字节站位破除伪共享是有意义的,因为Sequence的value属性是volatile修饰的,破除伪共享好处在于Sequence的value在cpu的缓存中始终都占用一行,不会因为其他变量的更新导致整个缓存行失效,从而cpu又重新去内存中读取,而是直接基于cpu缓存操作,效率肯定会比去读内存要快(伪共享产生的原因是因为:操作系统的局部性原理 + 类似volatile修饰的变量(可见性),很多博客文章只是一味的强调了操作系统的局部性原理,忽略了变量的可见性,而Java中的volatile关键字提供了可见性,所以理论上来说伪共享问题的产生有2个必要条件:操作系统的局部性原理 + cpu缓存行中的变量被要求具有可见性)

为什么要强调2个必要条件,因为如果缓存行中的变量不要求可见性的话,那么缓存行也不会失效,也不存在伪共享的问题。

关于操作系统的局部性原理和伪共享 大家可以参考这篇博文《RocketMQ技术内幕》一书作者的博文:https://codingw.blog.csdn.net/article/details/120814548

**注意:**关于笔者推荐的这篇文章,笔者认为作者在举关于伪共享中用的RingBuffer的getIndex,writeIndex指针的例子不恰当,原因如下(大家如果看完上面的源码分析,应该会明白)

1)首先disruptor 源码中没有getIndex,writeIndex指针,disruptor 主要通过Sequence的value值与RingBuffer长度取模来写或者读数组

2)笔者理解作者用getIndex,writeIndex指针举例,有一点强行解释 “RingBuffer破解伪共享”论点的意味

虽然笔者不认同文章中以上的2点,但是该篇文章空间换时间、以及其他观点是非常认同的,同时也认为文章写的非常好
关于伪共享优化性能实验,大家可以参考美团技术博文:https://tech.meituan.com/2016/11/18/disruptor.html

参考文章
https://codingw.blog.csdn.net/article/details/120814548
https://tech.meituan.com/2016/11/18/disruptor.html
https://lmax-exchange.github.io/disruptor/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值