2、Disruptor源码解读-生产者

353 阅读4分钟

上篇文章讲到,整个disruptor核心设计就是围绕Ringbuffer展开,本篇文章将会详细的介绍生产者是如何向Ringbuffer中写入数据的,耐心看完,就能理解单个生产者、多个生产者是如何把事件发布到RingBuffer里。

什么是RingBuffer

开始分析生产者流程之前,先简单介绍下什么是Ringbuffer。

其底层采用用数组Object[] entries存储元素,不会扩容,满了之后从数组头开始填充,即所谓环形数组。

在RingBuffer初始化的时候,就用“事件传递器”对象填充entries,初始化后不再新对象 ,消除了不断new对象带来的开销,只对entries中的Event对象做update操作。

同时采用了预填充变量,尽量避免了CPU“伪共享”。

生产者构成

分析之前,简单介绍下生产者的构成

  • Event
  • "事件传递器"
  • EventFactory
  • EventHandler
  • RingBuffer
  • Disruptor

Event,即我们要发布的事件,比如加入购物车、下单等具体的业务操作,内部可能包含请求参数,当前事件类型等;

“事件传递器”,(非官方命名,作者原创)用来填充RingBuffer,生产者和消费者之间的桥梁,生产者把事件交给“事件传递器”,消费者从“事件传递器”获取对应的事件;

EventFactory,工厂类,结合EventTranslator一起用,生产“事件传递器”;

EventHandler,事件处理器,用来处理具体的事件,即消费者,之所以单独列出来,是想让大家分清其和“事件传递器”的区别(刚开始看代码的时候,没绕出来);

Disruptor类,框架入口,创建Disruptor时候要指定一些核心参数,用来创建对应的RingBuffer;

    public Disruptor(
            final EventFactory<T> eventFactory, // 工厂类
            final int ringBufferSize,           // ringBuffer的大小
            final ThreadFactory threadFactory,  // 线程工厂,多个消费者会用到
            final ProducerType producerType,    // 生产者类型,当前是单生产者,还是多生产者
            final WaitStrategy waitStrategy)    // 等待策略,当生产速度比较慢的时候,怎么等待新的消息过来
    {
        this(
            RingBuffer.create(producerType, eventFactory, ringBufferSize,waitStrategy),
            new BasicExecutor(threadFactory));
    }

一次数据生产流程

趣解消费者-第 1 页.drawio.png

可以看到,当我们的创建Disruptor的时候,内部会进行RingBuffer的初始化,可以选择是初始化单一生产者,还是多生产的RingBuffer,区别就在于内部记录生产序号的Sequence实现类不同(后面文章会详细介绍)

调用对应的EventFactory的newInstance方法,提前初始化“事件传递器”,用来填充RingBuffer。

 RingBufferFields(EventFactory<E> eventFactory, Sequencer sequencer){
        ... ...
        this.entries = new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD];
        fill(eventFactory);
    }

    private void fill(EventFactory<E> eventFactory)
    {
        for (int i = 0; i < bufferSize; i++)
        {
            // 环形缓冲区里,每一个节点,放的都是eventFactory.newInstance()对象
            // 典型的空间换时间
            // BUFFER_PAD, 用于在数组中进行缓存行填充的空元素个数
            entries[BUFFER_PAD + i] = eventFactory.newInstance();
        }
    }

既然RingBuffer内部会提前创建很多“事件传递器”,那么消费者,是怎么把生产的事件,交给环上的“事件传递器”的呢?可以看下对应的发布事件动作。

    public <A> boolean tryPublishEvent(EventTranslatorOneArg<E, A> translator, A arg0)
    {
        try
        {
            // 取序号,来决定自己要写入到环上的哪个节点里
            final long sequence = sequencer.tryNext();
            //调用translator,把发布的事件,传递给“事件发布者”
            translateAndPublish(translator, sequence, arg0);
            return true;
        }
        catch (InsufficientCapacityException e)
        {
            return false;
        }
    }
    
    private <A> void translateAndPublish(EventTranslatorOneArg<E, A> translator, long sequence, A arg0)
    {
        try
        {
            // 根据序号,获取到对应的“事件发布器”
            // 调用translateTo实现,把要发布的事件,传递给“事件发布器”
            translator.translateTo(get(sequence), sequence, arg0);
        }
        finally
        {
            // 最后,提交seq,表示,当前的环已经被占用了,后续有事件发布,请选用其他的位置
            sequencer.publish(sequence);
        }
    }

根据序号管理器,首先获取自己能写入到RingBuffer的序号,根据序号,获取自己能写入的“事件发布器”,把对应的事件交给“事件发布器”,提交序号,表明当前“事件发布器”已经被占用。

RingBuffer类图

image.png 最后,放上一张RingBuffer类图,可以看到,RingBuffer的核心,其实是Sequenced,通过Sequenced,管理生产的“进度”,同时,也依赖管理Sequenced,实现多消费者高效并行消费,后续文章会详细介绍,勿急~。

小结

趣解消费者-第 3 页.drawio.png 想清晰的理解Disruptor的生产流程,关键就要弄明白RingBuffrer里,是怎么存储事件的。

首先创建Disruptor初始化RingBuffer的时候,会根据配置,提前初始化N个“事件传递器”(一般会多于配置数量,缓存行空填充 官方解释

生产者发布事件的时候,通过“序号管理器”获取到对应的“事件传递器”,把事件交给对应的“传递器”,即发布事件。

更新“序号管理器”,表示这个“传递器”已经被占用,其他生产者生产消息的时候,要请申请其他的“传递器”。