当JDK的ArrayBlockingQueue还在用锁的时候,Disruptor已经飞向了外太空!
一、开场:性能对比震撼登场⚡
实测数据(单生产者单消费者)
| 队列类型 | 吞吐量(ops/sec) | 性能倍数 |
|---|---|---|
| ArrayBlockingQueue | 500万 | 1x |
| LinkedBlockingQueue | 300万 | 0.6x |
| ConcurrentLinkedQueue | 800万 | 1.6x |
| Disruptor | 5000万 | 10x 🔥 |
没错,Disruptor比JDK自带的队列快10倍!
谁在用Disruptor?
- 🏦 金融交易系统(LMAX Exchange)
- 📊 Log4j2(异步日志)
- 🌐 Apache Storm(流处理)
- 🎮 游戏服务器(高并发)
二、为什么这么快?揭秘5大黑科技🔬
黑科技1:RingBuffer环形数组
传统队列(链表):
[头] → [节点1] → [节点2] → [节点3] → [尾]
问题:
- 每次入队都要
new对象(GC压力大)💸 - 节点分散在内存各处(缓存不友好)🐌
- 指针跳转(性能差)
Disruptor的RingBuffer(数组):
[0][1][2][3][4][5][6][7]
↑ ↑
头指针 尾指针
优势:
- ✅ 预分配对象,零GC
- ✅ 连续内存,CPU缓存友好
- ✅ 数组访问,速度极快
生活类比:
传统队列像火车🚂:
- 每节车厢都要单独制造
- 车厢之间用挂钩连接
RingBuffer像旋转寿司🍣:
- 盘子提前放好(预分配)
- 转一圈回来重复使用
- 位置固定,取用快速
黑科技2:无锁设计(CAS)
ArrayBlockingQueue(有锁):
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); // 加锁,慢!
try {
if (count == items.length)
return false;
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
Disruptor(无锁):
public long next() {
long current;
long next;
do {
current = cursor.get();
next = current + 1;
} while (!cursor.compareAndSet(current, next)); // CAS,快!
return next;
}
对比:
- 锁:线程阻塞、上下文切换、开销大
- CAS:自旋重试、无阻塞、开销小
黑科技3:消除伪共享(Cache Line Padding)
什么是伪共享?
CPU缓存以缓存行(Cache Line)为单位加载,通常是64字节。
问题场景:
class Counter {
volatile long value1; // 8字节
volatile long value2; // 8字节,和value1在同一缓存行
}
CPU核心1修改value1 → 整个缓存行失效
CPU核心2读取value2 → 缓存未命中,从内存重新加载
即使value1和value2逻辑上无关,也会互相影响!这就是伪共享。
Disruptor的解决方案:填充
class LhsPadding {
protected long p1, p2, p3, p4, p5, p6, p7; // 7 * 8 = 56字节
}
class Value extends LhsPadding {
protected volatile long value; // 8字节
}
class RhsPadding extends Value {
protected long p9, p10, p11, p12, p13, p14, p15; // 7 * 8 = 56字节
}
// 总共:56 + 8 + 56 = 120字节,独占两个缓存行!
生活类比:
不填充:你和室友住一间房🏠,一个人开灯会影响另一个人睡觉。
填充后:你们各住一间房,互不干扰!
黑科技4:序号栅栏(Sequence Barrier)
传统队列需要判断"队列是否满""队列是否空",需要锁。
Disruptor用序号:
生产者序号:cursor = 10
消费者序号:sequence = 8
队列大小:bufferSize = 8
可用空间:bufferSize - (cursor - sequence) = 8 - (10 - 8) = 6
无需锁,直接计算!
黑科技5:批量处理
传统队列:
// 每次take一个元素
for (int i = 0; i < 1000; i++) {
queue.take(); // 1000次锁竞争
}
Disruptor:
// 批量获取序号
long nextSequence = ringBuffer.next(batchSize);
// 只竞争一次!
for (long i = current; i <= nextSequence; i++) {
Event event = ringBuffer.get(i);
// 处理
}
对比:
- 传统:1000次竞争
- Disruptor:1次竞争
三、核心概念图解🎨
RingBuffer
[0][1][2][3][4][5][6][7]
↑ ↑ ↑
消费者1 生产者 消费者2
(Seq=2) (Cursor=5) (Seq=4)
Producer → 写入数据到slot
RingBuffer → 环形数组,预分配对象
Sequence → 序号(原子递增)
EventHandler → 消费者处理逻辑
WaitStrategy → 等待策略
四、快速上手:Hello Disruptor🎯
Maven依赖
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
示例:日志系统
// 1. 定义事件(数据载体)
public class LogEvent {
private String message;
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
// 2. 定义事件工厂(预分配对象)
public class LogEventFactory implements EventFactory<LogEvent> {
@Override
public LogEvent newInstance() {
return new LogEvent();
}
}
// 3. 定义事件处理器(消费者)
public class LogEventHandler implements EventHandler<LogEvent> {
@Override
public void onEvent(LogEvent event, long sequence, boolean endOfBatch) {
System.out.println("处理日志: " + event.getMessage() + " [seq=" + sequence + "]");
}
}
// 4. 创建Disruptor并启动
public class DisruptorDemo {
public static void main(String[] args) throws Exception {
// 队列大小(必须是2的幂)
int bufferSize = 1024;
// 创建Disruptor
Disruptor<LogEvent> disruptor = new Disruptor<>(
new LogEventFactory(), // 事件工厂
bufferSize, // 队列大小
Executors.defaultThreadFactory(), // 线程工厂
ProducerType.SINGLE, // 单生产者
new YieldingWaitStrategy() // 等待策略
);
// 注册事件处理器
disruptor.handleEventsWith(new LogEventHandler());
// 启动
disruptor.start();
// 获取RingBuffer
RingBuffer<LogEvent> ringBuffer = disruptor.getRingBuffer();
// 发布事件
for (int i = 0; i < 10; i++) {
long sequence = ringBuffer.next(); // 申请下一个序号
try {
LogEvent event = ringBuffer.get(sequence);
event.setMessage("日志消息-" + i);
} finally {
ringBuffer.publish(sequence); // 发布
}
}
Thread.sleep(1000);
disruptor.shutdown();
}
}
输出:
处理日志: 日志消息-0 [seq=0]
处理日志: 日志消息-1 [seq=1]
处理日志: 日志消息-2 [seq=2]
...
五、等待策略(WaitStrategy)选择🎛️
Disruptor提供多种等待策略,性能和CPU占用各不相同:
1. BlockingWaitStrategy(阻塞等待)
new BlockingWaitStrategy()
特点:
- 使用锁和Condition
- CPU占用最低
- 延迟最高(毫秒级)
适用场景: CPU资源紧张,对延迟不敏感
生活类比: 快递到了再通知你,你平时该干啥干啥🛋️
2. SleepingWaitStrategy(睡眠等待)
new SleepingWaitStrategy()
特点:
- 先自旋,再yield,最后sleep
- CPU占用低
- 延迟中等(微秒-毫秒)
适用场景: 平衡性能和CPU占用
生活类比: 先等1分钟,不来就躺会儿😴
3. YieldingWaitStrategy(让步等待)
new YieldingWaitStrategy()
特点:
- 先自旋,再Thread.yield()
- CPU占用中等
- 延迟低(纳秒-微秒)
适用场景: 高性能且有多余CPU核心
生活类比: 一直等,但累了让别人先来🤝
4. BusySpinWaitStrategy(忙碌自旋)⭐最快
new BusySpinWaitStrategy()
特点:
- 死循环等待
- CPU占用100%(独占核心)
- 延迟最低(纳秒级)
适用场景: 极致低延迟,且有专用CPU核心
生活类比: 一刻不停地刷新快递状态📱
性能对比表
| 策略 | 延迟 | CPU占用 | 吞吐量 |
|---|---|---|---|
| BlockingWait | 毫秒 | 低 | 低 |
| SleepingWait | 微秒-毫秒 | 低 | 中 |
| YieldingWait | 纳秒-微秒 | 中 | 高 |
| BusySpinWait | 纳秒 | 100% | 最高 |
六、进阶:多消费者模式🎭
模式1:顺序消费(串行)
disruptor.handleEventsWith(handler1)
.then(handler2)
.then(handler3);
生产者 → Handler1 → Handler2 → Handler3
模式2:并行消费
disruptor.handleEventsWith(handler1, handler2, handler3);
┌→ Handler1
生产者 → ├→ Handler2
└→ Handler3
模式3:菱形依赖
disruptor.handleEventsWith(handler1, handler2)
.then(handler3);
┌→ Handler1 ↘
生产者 → Handler3
└→ Handler2 ↗
实战:订单处理
// 1. 解析订单
EventHandler<OrderEvent> parseHandler = ...;
// 2. 并行处理:扣库存 & 扣积分
EventHandler<OrderEvent> stockHandler = ...;
EventHandler<OrderEvent> pointsHandler = ...;
// 3. 发短信通知
EventHandler<OrderEvent> smsHandler = ...;
// 配置依赖关系
disruptor.handleEventsWith(parseHandler) // 先解析
.then(stockHandler, pointsHandler) // 并行扣库存和积分
.then(smsHandler); // 最后发短信
七、实战案例:高性能日志系统📝
需求: 日志写入不能阻塞业务线程。
对比方案
方案1:BlockingQueue
BlockingQueue<String> queue = new ArrayBlockingQueue<>(1024);
// 生产者
public void log(String msg) {
queue.offer(msg); // 可能失败
}
// 消费者
new Thread(() -> {
while (true) {
String msg = queue.take(); // 阻塞
writeToFile(msg);
}
}).start();
问题:
- 队列满了日志丢失
- 锁竞争影响性能
方案2:Disruptor(推荐)
public class AsyncLogger {
private final Disruptor<LogEvent> disruptor;
private final RingBuffer<LogEvent> ringBuffer;
public AsyncLogger() {
int bufferSize = 1024 * 64; // 64K缓冲
disruptor = new Disruptor<>(
LogEvent::new,
bufferSize,
new ThreadFactoryBuilder().setNameFormat("log-disruptor-%d").build(),
ProducerType.MULTI, // 多生产者
new YieldingWaitStrategy()
);
// 注册处理器
disruptor.handleEventsWith(new LogEventHandler());
// 异常处理
disruptor.setDefaultExceptionHandler(new LogExceptionHandler());
disruptor.start();
ringBuffer = disruptor.getRingBuffer();
}
// 异步写日志
public void info(String msg) {
long sequence = ringBuffer.next();
try {
LogEvent event = ringBuffer.get(sequence);
event.setLevel("INFO");
event.setMessage(msg);
event.setTimestamp(System.currentTimeMillis());
} finally {
ringBuffer.publish(sequence);
}
}
public void shutdown() {
disruptor.shutdown();
}
}
// 事件处理器:批量写入文件
class LogEventHandler implements EventHandler<LogEvent> {
private BufferedWriter writer;
private List<String> buffer = new ArrayList<>(1000);
@Override
public void onEvent(LogEvent event, long sequence, boolean endOfBatch) {
buffer.add(event.format());
// 批量刷盘(性能优化)
if (endOfBatch || buffer.size() >= 1000) {
flush();
}
}
private void flush() {
try {
for (String line : buffer) {
writer.write(line);
writer.newLine();
}
writer.flush();
buffer.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
}
性能对比:
| 方案 | QPS | 99分位延迟 |
|---|---|---|
| BlockingQueue | 50万/秒 | 10ms |
| Disruptor | 500万/秒 | 50μs |
**提升10倍!**🚀
八、Disruptor vs BlockingQueue对比表📊
| 特性 | ArrayBlockingQueue | Disruptor |
|---|---|---|
| 数据结构 | 数组 | RingBuffer(数组) |
| 并发控制 | ReentrantLock | CAS无锁 |
| 对象分配 | 入队时new | 预分配,零GC |
| 缓存友好 | 一般 | 极致优化(填充) |
| 批量处理 | 不支持 | 天然支持 |
| 吞吐量 | 500万/秒 | 5000万/秒 |
| 延迟 | 毫秒级 | 纳秒级 |
| 学习成本 | 低 | 较高 |
九、适用场景与不适用场景✅❌
✅ 适合场景
- 高性能要求(金融、交易)
- 日志系统(异步写入)
- 事件驱动架构
- 流处理系统
- 游戏服务器
❌ 不适合场景
- 简单场景(大材小用)
- 阻塞操作多(浪费CPU)
- 内存受限(预分配占内存)
- 团队不熟悉(维护成本高)
十、常见问题FAQ🤔
Q1: bufferSize为什么必须是2的幂?
A: 为了用位运算代替取模运算:
// 取模(慢)
int index = sequence % bufferSize;
// 位运算(快)
int index = sequence & (bufferSize - 1); // bufferSize=8时,掩码=7(0111)
性能差距:位运算比取模快10倍!
Q2: 如何选择单生产者还是多生产者?
A:
- 单生产者(
ProducerType.SINGLE):性能更高 - 多生产者(
ProducerType.MULTI):更通用
如果确定只有一个生产者,一定要用SINGLE!
Q3: RingBuffer满了怎么办?
A:
- 默认策略:阻塞等待
- 自定义:实现
EventTranslator处理背压
Q4: 如何处理慢消费者?
A:
- 增加消费者数量(并行)
- 优化消费逻辑
- 扩大bufferSize
- 批量处理
十一、总结:该不该用Disruptor?🎯
决策树
需要极致性能吗?
├─ 不需要 → 用JDK的队列
└─ 需要
├─ 团队熟悉吗?
│ ├─ 不熟悉 → 先评估学习成本
│ └─ 熟悉 → 继续
├─ 有专用CPU核心吗?
│ ├─ 没有 → 谨慎使用BusySpinWait
│ └─ 有 → 可以用
└─ 选择Disruptor ✅
最佳实践
- 测试对比:先用BlockingQueue,有瓶颈再换Disruptor
- 监控指标:吞吐量、延迟、CPU占用
- 合理配置:bufferSize、WaitStrategy
- 批量处理:利用
endOfBatch标志 - 异常处理:设置ExceptionHandler
十二、彩蛋:Disruptor的诞生故事📖
Disruptor诞生于LMAX Exchange(伦敦多资产交易所),这是一家金融公司。
他们发现:现有的队列都太慢了!
于是,几个工程师在2011年开发了Disruptor,性能提升了10倍,成为开源传奇!
核心理念:
"Mechanical Sympathy" —— 对硬件的深刻理解
了解CPU缓存、分支预测、伪共享,才能写出极致性能的代码!
下期预告: 如何手写一个高性能的生产者-消费者队列?原理深度剖析!🛠️