Disruptor消费者协作模式源码分析

【源码笔记】专注于Java后端系列框架源码分析,Github地址:https://github.com/yuanmabiji/Java-SourceCode-Blogs

1 前言

Disruptor广播模式与执行顺序链源码分析 这篇文章中主要讲了Disruptor广播模式与执行链的构建原理,Disruptor广播模式跟MQ的广播模式功能是一样的即生产者生产的消息会广播到每个消费者;不过相信大家在使用MQ的过程中更多使用的是MQ的协作者模式即同一个消费者组的消费者共同消费生产者生产的消息,同样,Disruptor也一样提供了协作者模式,本篇文章将来探索Disruptor消费者协作者模式源码,不过如果Disruptor广播模式与执行顺序链源码 搞懂的话,本篇文章并不算难。

2 协作模式DEMO

同样,先来运行一个DEMO,后面再进入源码分析环节。

/**
 * Disruptor消费者协作模式:类似于MQ的消费者组模式
 */
public class LongEventMain
{
    private static final int BUFFER_SIZE = 1;
    public static void main(String[] args) throws Exception
    {
        // 1 创建RingBuffer
        RingBuffer<LongEvent> ringBuffer =
                RingBuffer.create(ProducerType.SINGLE,
                        new LongEventFactory() {
                            public LongEvent newInstance() {
                                return new LongEvent();
                            }
                        },
                        BUFFER_SIZE,
                        new YieldingWaitStrategy());

        //2 通过ringBuffer创建一个消费者与生产者之间的屏障,避免消费者消费速度赶上生产者
        SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();

        //3 创建多个消费者数组来协作消费生产者生产的数据,类似于mq的消费者组模式
        WorkHandler[] workHandlers = new LongEventHandler[5];
        for(int i = 0; i < workHandlers.length; i++) {
            workHandlers[i] = new LongEventHandler("WorkHandler-" + i);
        }

        //4 构建多消费者协作模式工作池
        WorkerPool<LongEvent> workerPool = new WorkerPool<LongEvent>(
                ringBuffer,
                sequenceBarrier,
                new EventExceptionHandler(),
                workHandlers);

        //5 设置GatingSequences,创建一个生产者与消费者组之间的屏障,避免生产者生产速度赶上消费最慢的消费者
        ringBuffer.addGatingSequences(workerPool.getWorkerSequences());

        //6 启动多消费者协作模式工作池workerPool
        workerPool.start(Executors.newFixedThreadPool(10));

        // 7 生产者生产10条数据
        for (int i = 0; i < 10; i++) {
            long sequence = ringBuffer.next();
            try {
                LongEvent longEvent = ringBuffer.get(sequence);
                longEvent.set(i);
            } finally {
                ringBuffer.publish(sequence);
            }
        }

    }

    static class LongEvent
    {
        private long value;

        public void set(long value)
        {
            this.value = value;
        }

        public long get() {
            return this.value;
        }
    }

    static class LongEventFactory implements EventFactory<LongEvent>
    {
        @Override
        public LongEvent newInstance()
        {
            return new LongEvent();
        }
    }

    static class LongEventHandler implements WorkHandler<LongEvent>
    {
        private String name;
        public LongEventHandler(String name) {
            this.name = name;
        }
        @Override
        public void onEvent(LongEvent event) throws Exception {
            System.out.println(Thread.currentThread().getName() + ":" + this.name + " consumes " + event.value);
        }
    }

    static class EventExceptionHandler implements ExceptionHandler<LongEvent> {
        public void handleEventException(Throwable ex, long sequence, LongEvent event) {
        }

        public void handleOnStartException(Throwable ex) {
        }

        public void handleOnShutdownException(Throwable ex) {
        }
    }
    
}

运行结果:

baaa53d9491d6ef4dae476666424b7e5.png

3 Disruptor消费者协作模式源码分析

因为Disruptor消费者协作模式源码跟Disruptor广播模式与执行顺序链源码 大同小异,这里主要分析下Disruptor消费者协作模式的实现思想和分析下跟Disruptor广播模式与执行顺序链源码 的区别之处。

下面这张图中有1个生产者和2个消费者,2个消费者共同消费生产者生产的数据,消费者们消费速度不能超过生产者生产的速度,生产者也不能超过最慢的那个消费者的消费速度。

b496319981fc9c22f1fac547070e3677.png

实现的核心思想如下:

  1. 首先构建消费者和生产者之间的sequenceBarrier屏障,sequenceBarrier底层逻辑实质是当前消费者的消费sequence跟生产者的生产sequence做比较,保证消费者消费不能赶上生产者。

    源码如下:

    // WorkProcessor.java
    public void run()
        {
    
      // 省略非关键源码...
            boolean processedSequence = true;
            long cachedAvailableSequence = Long.MIN_VALUE;
            long nextSequence = sequence.get();
            T event = null;
            // 死循环
            while (true)
            {
                try
                {
                    // if previous sequence was processed - fetch the next sequence and set
                    // that we have successfully processed the previous sequence
                    // typically, this will be true
                    // this prevents the sequence getting too far forward if an exception
                    // is thrown from the WorkHandler
                    if (processedSequence)
                    {
                        processedSequence = false;
                        // 【重要】这段逻辑是WorkProcessor和BatchEventProcessor消费逻辑的主要区别:当前消费者通过生产者workSequence的CAS操作来感知其他消费者消费进度,如果生产者workSequence被其他消费者消费了,那么就继续while循环,获取下一个生产者workSequence消费。这段代码也是多消费者协作模式轮流消费生产者数据的关键之处,因为nextSequence都是+1的,一个消费者获取到nextSequence后会进入业务处理逻辑,业务处理一般比较长时间,此时生产者的workSequence自然被下一个消费者消费,以此类推,保证了每个消费者轮流消费生产者数据的结果。
                        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);
                    }
                }
                catch (final TimeoutException e)
                {
                   // 省略非关键源码...
                }
                
            }
      // 省略非关键源码...
        }

    这段逻辑是WorkProcessorBatchEventProcessor消费逻辑的主要区别:这个do while循环(CAS)的目的主要是通过多消费者线程争夺生产者sequenceworkSequence的过程,当前消费者获取生产者workSequence后,然后给当前sequence设消费进度,但这两句不是原子操作,存在多线程问题, 但此时是多消费者并发消费,当前消费者获得生产者sequence时,此时可能其他消费者也会获取同一个生产者sequence,因此需要通过workSequence的CAS操作来判断当前消费者获取的生产者sequence有没有被其他消费者并发获取消费,如果有,那么继续循环获取下一个生产者workSequence;没有就消费获取到的当前生产者workSequence,从而保证了多消费者线程并发消费workSequence的线程安全问题。

  2. 其次构建生产者与消费者之间的gatingSequence屏障,gatingSequence屏障底层逻辑就是拿生产者生产sequence跟最慢的那个消费者的消费sequence做比较,保证生产速度不能赶上最慢消费者。源码实现同Disruptor广播模式与执行顺序链源码 。

4 小结

目前Disruptor源码分析文章就告一段落了,需要更详细源码注释请参见:https://github.com/yuanmabiji/disruptor 。目前开源中间件或框架应用Disruptor框架的不多,目前了解到的有CanalLog4j2Jstorm,百度uid-generator也借鉴了Disruptor的部分实现思路吧,另外,Disruptor在领域驱动模型中也有应用。也就是说Disruptor框架性能虽好,但其不流行,这又是说明原因呢?这个疑问暂且留到后面进行探讨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值