一、简介
Disruptor是一个高性能队列,研发的初衷是解决内部的内存队列的延迟问题,而不是分布式队列。基于Disruptor开发的系统单线程能支撑每秒600万订单。
使用场景:对延时要求很高的场景
二、高性能原理
1、无锁
内部实现都是cas+voilatile实现的,无锁性能比加锁更高
2、环形数组(关键)
好处:
- 数组比链表性能好,索引快
- 环形数组中的元素采用覆盖方式,避免了jvm频繁的GC。
- 数组的大小必须为2的n次方,元素定位可以通过位运算效率会更高
3、生产和消费模式
在Disruptor中生产者分为单生产者和多生产者,而消费者并没有区分。
单生产者情况下,就是普通的生产者向RingBuffer中放置数据,消费者获取最大可消费的位置,并进行消费。
多生产者时候,又多出了一个跟RingBuffer同样大小的Buffer,称为AvailableBuffer。在多生产者中,每个生产者首先通过CAS竞争获取可以写的空间,然后再进行慢慢往里放数据,如果正好这个时候消费者要消费数据,那么每个消费者都需要获取最大可消费的下标,这个下标是在AvailableBuffer进行获取得到的最长连续的序列下标。
4、Sequence下标指针
避免缓存行伪共享,增加了填充数据p1, p2, p3, p4, p5, p6, p7
cpu缓存行概念
cpu有多个缓存行cache,每个缓存行大小是32~128字节(通常是64字节)。
假设缓存行是64字节,而java的一个long类型是8字节,这样的话一个缓存行就可以存8个long类型的变量
cpu 每次从主内存中获取数据的时候都会将后面相邻的数据存入到同一个缓存行中。假设我们访问一个long内存对应的数组的时候,如果其中一个被加载到内存中,那么对应的后面的7个数据也会被加载到对应的缓存行中,这样就会非常快的访问数据
伪共享概念
当cpu缓存行中的数据被改变后,根据缓存一致性协议(MESI协议),整个缓存行就会失效,需要重新从主内存中加载数据。
表面上数据已经被加载到缓存行中,但是由于失效需要重新从主内存中获取数据,这会对性能就会造成很大的影响,这就是伪共享。
在java7中我们只能通过内存填充来解决这个问题,但是在java8中,提供了@sun.misc.Contended注解,替换了内存填充的工作。需要在启动jvm的时候要加入-XX:-RestrictContended 参数
4、使用Disruptor
1、引入依赖
<!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
2、定义事件数据LongEvent
@Data
public class LongEvent implements Serializable {
private long value;
}
3、定义事件工厂LongEventFactory
public class LongEventFactory implements EventFactory<LongEvent> {
public LongEvent newInstance() {
return new LongEvent();
}
}
4、定义生产者LongEventProducer
public class LongEventProducer {
private final RingBuffer<LongEvent> ringBuffer;
public LongEventProducer(RingBuffer<LongEvent> ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void produceData(long value) {
long sequence = ringBuffer.next(); // 获得下一个Event槽的下标
try {
// 给Event填充数据
LongEvent event = ringBuffer.get(sequence);
event.setValue(value);
} finally {
// 发布Event,激活观察者去消费, 将sequence传递给该消费者
// 注意,最后的 ringBuffer.publish() 方法必须包含在 finally 中以确保必须得到调用;如果某个请求的 sequence 未被提交,将会堵塞后续的发布操作或者其它的 producer。
ringBuffer.publish(sequence);
}
}
}
5、定义消费者LongEventHandler
public class LongEventHandler implements EventHandler<LongEvent> {
@Override
public void onEvent(LongEvent event, long sequence, boolean endOfBatch) throws Exception {
System.out.println("consumer:" + Thread.currentThread().getName() + " Event: value=" + event.getValue() + ",sequence=" + sequence + ",endOfBatch=" + endOfBatch);
}
}
6、main方法
public class Main {
public static void main(String[] args) {
long beginTime = System.currentTimeMillis();
// 定义用于事件处理的线程池,Disruptor 通过 java.util.concurrent.ExecutorService 提供的线程来触发 Consumer 的事件处理
ExecutorService executor = Executors.newCachedThreadPool();
// 指定事件工厂
LongEventFactory factory = new LongEventFactory();
// 指定 ring buffer字节大小,必需为2的N次方(能将求模运算转为位运算提高效率 ),否则影响性能
int bufferSize = 1024 * 1024;
// 单线程模式,获取额外的性能
Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(factory, bufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
// 设置事件业务处理器---消费者
disruptor.handleEventsWith(new LongEventHandler());
// 启动disruptor线程
disruptor.start();
// 获取 ring buffer环,用于接取生产者生产的事件
RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
// 为 ring buffer指定事件生产者
LongEventProducer producer = new LongEventProducer(ringBuffer);
//LongEventProducerWithTranslator producer = new LongEventProducerWithTranslator(ringBuffer);
for (int i = 0; i<100000; i++) {
producer.produceData(i);// 生产者生产数据
}
disruptor.shutdown(); //关闭 disruptor,方法会堵塞,直至所有的事件都得到处理;
executor. shutdown(); //关闭 disruptor 使用的线程池;如果需要的话,必须手动关闭, disruptor 在 shutdown 时不会自动关闭;
System.out.println(String.format("总共耗时%s毫秒", (System.currentTimeMillis() - beginTime)));
}
}
Disruptor各类解释
- RingBuffer——Disruptor底层数据结构实现,核心类,是线程间交换数据的中转地;
- Sequencer——序号管理器,生产同步的实现者,负责消费者/生产者各自序号、序号栅栏的管理和协调,Sequencer有单生产者,多生产者两种不同的模式,里面实现了各种同步的算法;
- Sequence——序号,声明一个序号,用于跟踪ringbuffer中任务的变化和消费者的消费情况,disruptor里面大部分的并发代码都是通过对Sequence的值同步修改实现的,而非锁,这是disruptor高性能的一个主要原因;
- SequenceBarrier——序号栅栏,管理和协调生产者的游标序号和各个消费者的序号,确保生产者不会覆盖消费者未来得及处理的消息,确保存在依赖的消费者之间能够按照正确的顺序处理
- EventProcessor——事件处理器,监听RingBuffer的事件,并消费可用事件,从RingBuffer读取的事件会交由实际的生产者实现类来消费;它会一直侦听下一个可用的序号,直到该序号对应的事件已经准备好。
- EventHandler——消费者接口(业务处理器),第三方实现该接口,完成具体的业务逻辑实现。
- Producer——生产者接口,第三方线程充当该角色,producer向RingBuffer写入事件。
消费者常见的等待策略
- BlockingWaitStrategy阻塞等待策略(默认消费者等待策略)
内部使用的是典型的锁和条件变量机制,来处理线程的唤醒。这种策略是最慢的一种,是最保守使用消耗cpu的一种用法
- SleepingWaitStrategy休眠等待策略(测试环境一般使用)
也是一种保守使用cpu的策略。它使用一个简单的loop繁忙等待循环,但是在循环体种它调用了LockSupport.parkNanos(1)所以它适合在不需要低延迟,但需要很低的生产者线程影响的情形。适合异步日志记录功能
- YieldingWaitStrategy服从等待策略(生产环境降低延迟)
它是2种可以用于低延迟系统的等待策略之一,充分使用压榨cpu来达到降低延迟的目标。它不断的循环等待sequence去递增到合适的
- BusySpinWaitStrategy繁忙旋转等待策略
它是性能最高的等待策略。仅仅当event处理线程数少于物理核心数时才应该采用这种等待策略
- PhasedBackoffWaitStrategy阶段性退避等待策略
自旋 + yield + 自定义策略,CPU资源紧缺,吞吐量和延迟并不重要的场景。