Disruptor: 为什么这么快?深入源码解析

441 阅读4分钟

Disruptor: 为什么这么快?深入源码解析

引言

前面几期介绍了Disruptor的高性能特性,以及常用的使用场景。那么 Disruptor 性能为什么高, 为什么这么快?它是如何做到的?本文将深入其设计原理并结合源码进行解析。

Disruptor 的核心设计

1. 环形数组(Ring Buffer)

传统的 BlockingQueue 基于链表或数组实现,受制于锁竞争和 GC 影响。而 Disruptor 采用无锁的环形数组 RingBuffer,避免了锁带来的开销。

public class RingBuffer<T> {
    private final Object[] entries;
    private final int indexMask;
    
    public RingBuffer(int bufferSize) {
        this.entries = new Object[bufferSize];
        this.indexMask = bufferSize - 1;
    }
    
    public T get(long sequence) {
        return (T) entries[(int) (sequence & indexMask)];
    }
}

优化点:索引采用 sequence & indexMask 进行取模,indexMask = bufferSize - 1 仅适用于 bufferSize 为 2 的幂次方的情况,利用 & 代替 %,减少 CPU 指令数,提高访问速度。

2. 无锁序列控制(Sequence & Cursor)

传统队列使用锁来控制访问,而 Disruptor 通过 Sequence 变量实现线程间同步,避免了锁的开销。

public final class Sequence {
    private volatile long value;
    
    public long get() {
        return value;
    }
    
    public void set(long value) {
        this.value = value;
    }
}

优化点

  • volatile 保证可见性,避免 CPU 缓存导致的数据不一致。
  • 使用 CAS(Compare-And-Swap)更新 Sequence,避免线程锁争用,提高并发性能。
  • CAS 机制通过 CPU 指令级别的原子操作来完成,无需加锁,因此比传统的 synchronized 更高效。

3. 多生产者-多消费者的高效协调(Sequencer & WaitStrategy)

Disruptor 通过 Sequencer 控制生产者和消费者的进度,提供 SingleProducerSequencerMultiProducerSequencer 以适配不同的场景。

单生产者模式 (SingleProducerSequencer)
public final class SingleProducerSequencer extends AbstractSequencer {
    public synchronized long next() {
        long nextSequence = cursor.get() + 1;
        cursor.set(nextSequence);
        return nextSequence;
    }
}

优化点:单生产者场景下可以使用 synchronized,由于只有一个生产者,避免了 CAS 失败导致的重试,提高了吞吐量。

多生产者模式 (MultiProducerSequencer)
public final class MultiProducerSequencer extends AbstractSequencer {
    public long next() {
        long current;
        long next;
        do {
            current = cursor.get();
            next = current + 1;
        } while (!cursor.compareAndSet(current, next));
        return next;
    }
}

优化点

  • 采用 CAS 进行 cursor 更新,确保并发安全。
  • 避免 synchronized,降低线程上下文切换开销,提高吞吐量。

4. 缓存行填充(False Sharing 避免)

伪共享(False Sharing) 是指多个 CPU 线程访问的变量共享同一个 CPU 缓存行,导致缓存一致性协议频繁触发,从而降低系统性能。

class PaddedLong {
    public volatile long value = 0L;
    public long p1, p2, p3, p4, p5, p6, p7;
}

优化点

  • 通过填充额外的无用 long 变量,确保 value 不与其他变量共享缓存行,避免缓存一致性冲突。
  • 这在多线程场景下可显著降低 L3/L2 缓存一致性维护的开销。

5. 生产者和消费者的批量处理

DisruptorBatchEventProcessor 允许消费者一次性批量处理多个事件,而不是逐个消费,减少 CPU 上下文切换的开销。

public class BatchEventProcessor<T> {
    public void run() {
        while (running) {
            long nextSequence = sequence.get() + 1;
            T event = ringBuffer.get(nextSequence);
            eventHandler.onEvent(event);
            sequence.set(nextSequence);
        }
    }
}

优化点

  • 降低延迟:批量处理多个事件,减少消费者之间的频繁调度。
  • 提高吞吐量:减少 RingBuffer 访问次数,提高数据本地性。

性能对比分析

下表展示了 DisruptorBlockingQueue 在吞吐量(百万 ops/sec)上的对比(越高越好):

队列类型吞吐量(ops/sec)
ArrayBlockingQueue5M
LinkedBlockingQueue3M
ConcurrentLinkedQueue15M
Disruptor25M

结论

Disruptor 之所以快,主要得益于以下几点优化:

  1. 无锁并发:通过 CAS 和 Sequence 控制并发,无需加锁,避免锁竞争带来的性能损耗。
  2. 环形数组:避免 GC 和动态扩展带来的开销,保证了数据访问的局部性。
  3. 缓存行填充:减少 CPU 伪共享问题,提高缓存命中率和访问效率。
  4. 优化的等待策略:根据场景选择合适的 WaitStrategy,降低 CPU 开销,提高吞吐量。
  5. 高效的生产者-消费者协调机制:不同 Sequencer 适配不同并发场景,减少线程间同步开销。
  6. 批量处理:消费者可以一次性消费多个事件,减少 CPU 负担,提高吞吐量。

最后

欢迎关注,每天获取干货知识!