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 控制生产者和消费者的进度,提供 SingleProducerSequencer 和 MultiProducerSequencer 以适配不同的场景。
单生产者模式 (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. 生产者和消费者的批量处理
Disruptor 的 BatchEventProcessor 允许消费者一次性批量处理多个事件,而不是逐个消费,减少 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访问次数,提高数据本地性。
性能对比分析
下表展示了 Disruptor 和 BlockingQueue 在吞吐量(百万 ops/sec)上的对比(越高越好):
| 队列类型 | 吞吐量(ops/sec) |
|---|---|
ArrayBlockingQueue | 5M |
LinkedBlockingQueue | 3M |
ConcurrentLinkedQueue | 15M |
Disruptor | 25M |
结论
Disruptor 之所以快,主要得益于以下几点优化:
- 无锁并发:通过 CAS 和
Sequence控制并发,无需加锁,避免锁竞争带来的性能损耗。 - 环形数组:避免
GC和动态扩展带来的开销,保证了数据访问的局部性。 - 缓存行填充:减少 CPU 伪共享问题,提高缓存命中率和访问效率。
- 优化的等待策略:根据场景选择合适的
WaitStrategy,降低 CPU 开销,提高吞吐量。 - 高效的生产者-消费者协调机制:不同
Sequencer适配不同并发场景,减少线程间同步开销。 - 批量处理:消费者可以一次性消费多个事件,减少 CPU 负担,提高吞吐量。
最后
欢迎关注,每天获取干货知识!