Disruptor是一个高性能队列,研发的初衷是解决内部的内存队列的延迟问题,而不是分布式队列。基于Disruptor开发的系统单线程能支撑每秒600万订单。
使用场景:对延时要求很高的场景
java队列有哪些:java队列有哪些_现实、太残忍的博客-CSDN博客
内部实现都是cas+voilatile实现的,无锁性能比加锁更高
好处:
在Disruptor中生产者分为单生产者和多生产者,而消费者并没有区分。
单生产者情况下,就是普通的生产者向RingBuffer中放置数据,消费者获取最大可消费的位置,并进行消费。
多生产者时候,又多出了一个跟RingBuffer同样大小的Buffer,称为AvailableBuffer。在多生产者中,每个生产者首先通过CAS竞争获取可以写的空间,然后再进行慢慢往里放数据,如果正好这个时候消费者要消费数据,那么每个消费者都需要获取最大可消费的下标,这个下标是在AvailableBuffer进行获取得到的最长连续的序列下标。
避免缓存行伪共享,增加了填充数据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 参数
1、引入依赖
com.lmax
disruptor
3.3.4
2、定义事件数据LongEvent
@Data
public class LongEvent implements Serializable {
private long value;
}
3、定义事件工厂LongEventFactory
public class LongEventFactory implements EventFactory {
public LongEvent newInstance() {
return new LongEvent();
}
}
4、定义生产者LongEventProducer
public class LongEventProducer {
private final RingBuffer ringBuffer;
public LongEventProducer(RingBuffer 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 {
@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 disruptor = new Disruptor(factory, bufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
// 设置事件业务处理器---消费者
disruptor.handleEventsWith(new LongEventHandler());
// 启动disruptor线程
disruptor.start();
// 获取 ring buffer环,用于接取生产者生产的事件
RingBuffer 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)));
}
}
BlockingWaitStrategy阻塞等待策略(默认消费者等待策略)
内部使用的是典型的锁和条件变量机制,来处理线程的唤醒。这种策略是最慢的一种,是最保守使用消耗cpu的一种用法
SleepingWaitStrategy休眠等待策略(测试环境一般使用)
也是一种保守使用cpu的策略。它使用一个简单的loop繁忙等待循环,但是在循环体种它调用了LockSupport.parkNanos(1)所以它适合在不需要低延迟,但需要很低的生产者线程影响的情形。适合异步日志记录功能
它是2种可以用于低延迟系统的等待策略之一,充分使用压榨cpu来达到降低延迟的目标。它不断的循环等待sequence去递增到合适的
它是性能最高的等待策略。仅仅当event处理线程数少于物理核心数时才应该采用这种等待策略
自旋 + yield + 自定义策略,CPU资源紧缺,吞吐量和延迟并不重要的场景。