目录

系列索引

简介

运行架构

核心内容

核心概念

核心优化点

具体优化对比

VS BlockingQueue

消除伪共享

内存预分配与无锁


系列索引

  • Disruptor源码解析一 Disruptor高性能之道
  • Disruptor源码解析二 Sequence相关类解析
  • Disruptor源码解析三 RingBuffer解析
  • Disruptor源码解析四 消费者的组织串联
  • Disruptor源码解析五 消费者的具体实现
  • Disruptor源码解析六 示例与性能压测

简介

LMAX Disruptor是一个高性能的线程间消息库。它源于LMAX对并发性,性能和非阻塞算法的研究,如今构成了Exchange基础架构的核心部分。

  • Disruptor它是一个开源的并发框架,并获得2011 Duke’s 程序框架创新奖,能够在无锁的情况下实现网络的Queue并发操作。
  • Disruptor是一个高性能的异步处理框架,或者可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式的实现,或者事件监听模式的实现。

运行架构

架构图:

dophinescheduler 源码启动 disruptor源码分析_java

具体运行示例图:

一个生产者P1与三个消费者C1、C2、C3,C3的事件处理需要C1与C2先完成。则该模型结构如下:

dophinescheduler 源码启动 disruptor源码分析_Data_02

核心内容

核心概念

  • Ring Buffer
  • 如其名,环形的缓冲区。曾经 RingBuffer 是 Disruptor 中的最主要的对象,但从3.0版本开始,其职责被简化为仅仅负责对通过 Disruptor 进行交换的数据(事件)进行存储和更新。在一些更高级的应用场景中,Ring Buffer 可以由用户的自定义实现来完全替代。
  • Sequence Disruptor
  • 通过顺序递增的序号来编号管理通过其进行交换的数据(事件),对数据(事件)的处理过程总是沿着序号逐个递增处理。一个 Sequence 用于跟踪标识某个特定的事件处理者( RingBuffer/Consumer )的处理进度。虽然一个 AtomicLong 也可以用于标识进度,但定义 Sequence 来负责该问题还有另一个目的,那就是防止不同的Sequence 之间的CPU缓存伪共享(Flase Sharing)问题。
  • Sequencer Sequencer
  • 是 Disruptor 的真正核心。此接口有两个实现类 SingleProducerSequencer、MultiProducerSequencer ,它们定义在生产者和消费者之间快速、正确地传递数据的并发算法。
  • Sequence Barrier
  • 用于保持对RingBuffer的 main published Sequence 和Consumer依赖的其它Consumer的 Sequence 的引用。 Sequence Barrier 还定义了决定 Consumer 是否还有可处理的事件的逻辑。
  • Wait Strategy
  • 定义 Consumer 如何进行等待下一个事件的策略。 (注:Disruptor 定义了多种不同的策略,针对不同的场景,提供了不一样的性能表现)
  • Event
  • 在 Disruptor 的语义中,生产者和消费者之间进行交换的数据被称为事件(Event)。它不是一个被 Disruptor 定义的特定类型,而是由 Disruptor 的使用者定义并指定。
  • EventProcessor
  • EventProcessor 持有特定消费者(Consumer)的 Sequence,并提供用于调用事件处理实现的事件循环(Event Loop)。
  • EventHandler
  • Disruptor 定义的事件处理接口,由用户实现,用于处理事件,是 Consumer 的真正实现。
  • Producer
  • 即生产者,只是泛指调用 Disruptor 发布事件的用户代码,Disruptor 没有定义特定接口或类型。

核心优化点

  • 减少垃圾回收
  • 让消息可以通过被多个消费者并行处理
  • 使用无锁算法来实现并发(CAS)
  • 缓存行填充

具体优化对比

VS BlockingQueue

  • producer只能从head放数据,producer之间会竞争head指针,存在写竞争。
  • consumer之间会竞争tail指针,它们之间也存在写竞争。
  • 并且很多情况下,queue是处于全空状态,head/tail指针指向同一个entry,producer和consumer之间也存在写竞争。因此需要lock来实现synchronization。
  • 另一个缺点是heal/tail指针的false sharing。

dophinescheduler 源码启动 disruptor源码分析_Data_03

两个Queue的对比测试代码, 单生产者单消费者的情况下,disruptor的处理速度(1E数据约7100ms)约是blockingQueue(1E数据约21000ms)的3倍 ,Disruptor代码 :

public class DisruptorSingle4Test {

    public static void main(String[] args) {
        int ringBufferSize = 65536;
        final Disruptor<Data> disruptor = new Disruptor<Data>(
                 new EventFactory<Data>() {
                     @Override
					public Data newInstance() {
						return new Data();
					}
				},
                ringBufferSize,
                Executors.newSingleThreadExecutor(),
                ProducerType.SINGLE,
                //new BlockingWaitStrategy()
                new YieldingWaitStrategy()
        		);

        DataConsumer consumer = new DataConsumer();
        //消费数据
        disruptor.handleEventsWith(consumer);
        disruptor.start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                RingBuffer<Data> ringBuffer = disruptor.getRingBuffer();
                // 约1E
                for (long i = 0; i < Integer.MAX_VALUE / 20; i++) {
                    long seq = ringBuffer.next();
                    Data data = ringBuffer.get(seq);
                    data.setId(i);
                    data.setName("c" + i);
                    ringBuffer.publish(seq);
                }
            }
        }).start();
    }

    public static class DataConsumer implements EventHandler<Data> {

        private long startTime;
        private int i;

        public DataConsumer() {
            this.startTime = System.currentTimeMillis();
        }

        @Override
        public void onEvent(Data data, long seq, boolean bool)
                throws Exception {
            i++;
            if (i == Constants.EVENT_NUM_OHM) {
                long endTime = System.currentTimeMillis();
                System.out.println("Disruptor costTime = " + (endTime - startTime) + "ms");
            }
        }
    }

    // event
    public static class Data implements Serializable {

        private static final long serialVersionUID = 2035546038986494352L;
        private Long id ;
        private String name;

        public Data() {
        }
        public Data(Long id, String name) {
            super();
            this.id = id;
            this.name = name;
        }

        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
}

BlockingQueue代码 :

public class ArrayBlockingQueue4Test {

    public static void main(String[] args) {
        final ArrayBlockingQueue<Data> queue = new ArrayBlockingQueue<Data>(100000000);
        final long startTime = System.currentTimeMillis();
        //向容器中添加元素
        new Thread(new Runnable() {

            @Override
            public void run() {
                long i = 0;
                // 约1E
                while (i < Integer.MAX_VALUE / 20) {
                	Data data = new Data(i, "c" + i);
                    try {
                        queue.put(data);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    i++;
                }
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                int k = 0;
                // 约1E
                while (k < Integer.MAX_VALUE / 20) {
                    try {
                        queue.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    k++;
                }
                long endTime = System.currentTimeMillis();
                System.out.println("ArrayBlockingQueue costTime = " + (endTime - startTime) + "ms");
            }
        }).start();
    }
}

消除伪共享

CPU缓存

  • 由缓存行组成,通常是 64 字节(常用处理器的缓存行是 64 字节的,比较旧的处理器缓存行是 32 字节),并且它有效地引用主内存中的一块地址。一个 Java 的 long 类型是 8 字节,因此在一个缓存行中可以存 8 个 long 类型的变量。

dophinescheduler 源码启动 disruptor源码分析_System_04

缓存行对代码的性能影响对比, 2E数据的场景下,正常处理时间10.5s,消除伪共享处理时间1.5s,处理性能约是10倍,提升巨大。

public class FalseSharingTest {

    public static void main(String[] args) throws InterruptedException {
        testPointer(new Pointer());
    }

    private static void testPointer(Pointer pointer) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < Integer.MAX_VALUE / 10; i++) {
                pointer.x++;
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < Integer.MAX_VALUE / 10; i++) {
                pointer.y++;
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(System.currentTimeMillis() - start);
        System.out.println(pointer);
    }

    static class Pointer {
        volatile long x;
        // 打开这里的注释可以开启缓存行处理时长对比
        //long p1, p2, p3, p4, p5, p6, p7;
        volatile long y;
    }

    static class Pointer2 {
        MyLong x = new MyLong();
        MyLong y = new MyLong();
    }

    static class MyLong {
        volatile long value;
        long p1, p2, p3, p4, p5, p6, p7;
    }
}

内存预分配与无锁

参考RingBuffer, 预先构造ringBuffer 内存对象。

利用Sequencer协调多个生产者,sequenceBarrier协调多个消费者,无锁并发。