Disruptor:无锁并发的终极武器🚀

130 阅读8分钟

当JDK的ArrayBlockingQueue还在用锁的时候,Disruptor已经飞向了外太空!

一、开场:性能对比震撼登场⚡

实测数据(单生产者单消费者)

队列类型吞吐量(ops/sec)性能倍数
ArrayBlockingQueue500万1x
LinkedBlockingQueue300万0.6x
ConcurrentLinkedQueue800万1.6x
Disruptor5000万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();
        }
    }
}

性能对比:

方案QPS99分位延迟
BlockingQueue50万/秒10ms
Disruptor500万/秒50μs

**提升10倍!**🚀


八、Disruptor vs BlockingQueue对比表📊

特性ArrayBlockingQueueDisruptor
数据结构数组RingBuffer(数组)
并发控制ReentrantLockCAS无锁
对象分配入队时new预分配,零GC
缓存友好一般极致优化(填充)
批量处理不支持天然支持
吞吐量500万/秒5000万/秒
延迟毫秒级纳秒级
学习成本较高

九、适用场景与不适用场景✅❌

✅ 适合场景

  1. 高性能要求(金融、交易)
  2. 日志系统(异步写入)
  3. 事件驱动架构
  4. 流处理系统
  5. 游戏服务器

❌ 不适合场景

  1. 简单场景(大材小用)
  2. 阻塞操作多(浪费CPU)
  3. 内存受限(预分配占内存)
  4. 团队不熟悉(维护成本高)

十、常见问题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 ✅

最佳实践

  1. 测试对比:先用BlockingQueue,有瓶颈再换Disruptor
  2. 监控指标:吞吐量、延迟、CPU占用
  3. 合理配置:bufferSize、WaitStrategy
  4. 批量处理:利用endOfBatch标志
  5. 异常处理:设置ExceptionHandler

十二、彩蛋:Disruptor的诞生故事📖

Disruptor诞生于LMAX Exchange(伦敦多资产交易所),这是一家金融公司。

他们发现:现有的队列都太慢了!

于是,几个工程师在2011年开发了Disruptor,性能提升了10倍,成为开源传奇!

核心理念:

"Mechanical Sympathy" —— 对硬件的深刻理解

了解CPU缓存、分支预测、伪共享,才能写出极致性能的代码!


下期预告: 如何手写一个高性能的生产者-消费者队列?原理深度剖析!🛠️