Disruptor是一个用于在线程间通信的高效低延时的消息组件,它像个增强的队列,使用栅栏(barrier)+序号(Sequencing)机制协调生产者与消费者,从而避免使用锁和CAS,同时还组合使用预分配内存机制、缓存行机制(cache line)、批处理效应(batch effect)来达到高吞吐量和低时延的目标。
相关背景概述
- 并发的复杂性
代码并发执行主要考虑两个方面:互斥和变化的可见性。互斥主要用来管理对某些资源的竞争访问;变化的可见性主要用来控制什么时候这些变化对其他线程可见。并发环境中最耗时的操作其实就是并发写操作。多线程对同一个资源近与需要复杂昂贵的协调,通常会通过某种锁来实现资源协调:
1.锁的代价
锁提供了互斥,并能够确保变化能够以一个确定的顺序让其它的线程可见锁会涉及到操作系统的上下文切换,操作系统会挂起所有在等待这把锁的线程,直到锁持有者释放该锁
2.CAS的代价
CAS依赖于处理器的支持,CAS相对于锁是非常高效的,CAS并不是免费的,一般是采用CPU空转的方式
Disruptor的设计初衷就是为了解决锁代价和CAS代价过大等问题,以求最优化内存分配,使用缓存友好的方式来最佳使用硬件资源,Disruptor地核心机制在于:以RingBuffer的形式预分配有界的数据结构,单个或多个生产者可以抽RingBuffer中写入数据,单个或多个消费者可以从RingBuffer读取数据。
核心对象
- RingBuffer 一个环形的数据结构,对象初始化时,会使用事件Event进行填充,Buffer的大小必须是2的幂次方,方便移位操作。
- Event 无指定具体接口,用户自己实现,可以绑定自己的业务数据。
- EventFactory 生产事件Event的工厂,由用户自己实现(extends com.lmax.disruptor.EventFactory)
- EvnetTranslator 事件发布回调接口,由用户自己实现(extends com.lmax.disruptor.EventTranslator),调用translateTo方法负责将业务参数设置到事件中。
- Sequencer 序列产生器,也是协调生产者和消费者及实现高并发的核心,有MultiProducerSequencer和SingleProducerSequencer两个实现类。
- SequenceBarrier 拥有RingBuffer的发布事件Sequence引用和消费者依赖的Sequence引用。决定消费者消费可消费的Sequence。
- EventHandler 事件的处理者,由用户自己实现,消费者处理消息。
- EventProcesssor 事件的处理器,单独在一个线程中运行。持有特定消费者的Sequence,并提供用于调用事件处理实现的事件循环
- WorkHandler 事件的处理者,由用户自己实现。(个人感觉和EventHandler没多大区别)
- WorkProcessor 事件的处理器,单独在一个线程中运行。
- WorkerPool 一组WorkProcessor的处理。
- WaitStrategy 在消费者比生产者快时,消费者处理器的等待策略。
BlockingWaitStrategy:通过线程阻塞的方式,等待生产者唤醒
BusySpinWaitStrategy:线程一直自旋等待,比较耗CPU。
LiteBlockingWaitStrategy:通过线程阻塞的方式,等待生产者唤醒,比BlockingWaitStrategy要轻,某些情况下可以减少阻塞的次数。
PhasedBackoffWaitStrategy:根据指定的时间段参数和指定的等待策略决定采用哪种等待策略。
SleepingWaitStrategy:可通过参数设置,使线程通过Thread.yield()主动放弃执行,通过线程调度器重新调度;或一直自旋等待。
TimeoutBlockingWaitStrategy:通过参数设置阻塞时间,如果超时则抛出异常。
YieldingWaitStrategy: 通过Thread.yield()主动放弃执行,通过线程调度器重新调度。
实现原理及分析
- RingBuffer的实现:封装了一个对象数组,RingBuffer实例化时,用Event填充。生产者和消费者通过对序列(long的原子操作)取模计算获取对象数组中的Event.
public E get(long sequence) { return elementAt(sequence); }
protected final E elementAt(long sequence) { return (E) UNSAFE.getObject(entries, REF_ARRAY_BASE + ((sequence & indexMask) << REF_ELEMENT_SHIFT)); }
- 单个生产者的实现:保存有所有消费者当前消费的前一个序列值,在取下一个要发布的序列时,检查要发布的序列是否覆盖所有消费者正在处理的最小序列。如果未覆盖,则获取可发布的游标值,如果覆盖(说明缓存已经满了),则自旋等待,直到可以发布。发布事件时则先发布,后指定当前游标为发布的序列值。
- 多个生产者的实现:保存有所有消费者当前消费的前一个序列值,并维护一个和RingBuffer一样大小的数组,在取下一个要发布的序列时,检查要发布的序列是否覆盖所有消费者正在处理的最小序列。如果未覆盖,则先发布,后指定当前游标为发布的序列值,如果未覆盖,则获取可发布的游标值,如果覆盖(说明缓存已经满了),则自旋等待,直到可以发布。一个生产者获取可发布的序列后,立即更新当前游标。发布事件时生产者每发布一个序列,则记录到数组指定位置。
- 消费者的实现:消费者保持一个自己的序列,每次累加后nextSequence,去获取可访问的最大序列。对于一个生产者,就是nextSequence到RingBuffer当前游标的序列。对于多个生产者,就是nextSequence到RingBuffer当前游标之间,最大的连续的序列集。
DEMO实例
- POM
-
<dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.2</version> </dependency>
- 创建一个Event对象
-
package com.scgaopan.disruptor.pakingdemo; /** * Author:scgaopan * Date:18/10/25 * Description: 汽车信息 */ public class MyInParkingDataEvent { private String carLicense;//车牌号 public String getCarLicense() { return carLicense; } public void setCarLicense(String carLicense) { this.carLicense = carLicense; } }
- 创建监听类,消费者的具体实现,这里创建了3个消费者
-
package com.scgaopan.disruptor.pakingdemo.handler; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.WorkHandler; import com.scgaopan.disruptor.pakingdemo.MyInParkingDataEvent; /** * Author:scgaopan * Date:18/10/25 * Description:第一个消费者,负责保存进场汽车的信息 */ public class MyParkingDataInDbHandler implements EventHandler<MyInParkingDataEvent>, WorkHandler<MyInParkingDataEvent> { public void onEvent(MyInParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception { System.out.println("this is Eventhandler executed.........data="+event.getCarLicense()); this.onEvent(event); } public void onEvent(MyInParkingDataEvent event) throws Exception { System.out.println("this is WorkHandler executed.........data="+event.getCarLicense()); Thread.sleep(3000); System.out.println(" 成功保存车牌信息到database中,currentThread="+Thread.currentThread().getId()+",data="+event.getCarLicense()); } }
package com.scgaopan.disruptor.pakingdemo.handler; import com.lmax.disruptor.EventHandler; import com.scgaopan.disruptor.pakingdemo.MyInParkingDataEvent; /** * Author:scgaopan * Date:18/10/25 * Description: */ public class MyParkingDataToKafkaHandler implements EventHandler<MyInParkingDataEvent> { public void onEvent(MyInParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception { Thread.sleep(2000); System.out.println("通过kafka成功,当前线程="+Thread.currentThread().getId()+" data="+event.getCarLicense()); } }
package com.scgaopan.disruptor.pakingdemo.handler; import com.lmax.disruptor.EventHandler; import com.scgaopan.disruptor.pakingdemo.MyInParkingDataEvent; /** * Author:scgaopan * Date:18/10/25 * Description: */ public class MyParkingDataSmsHandler implements EventHandler<MyInParkingDataEvent> { public void onEvent(MyInParkingDataEvent event, long sequence, boolean endOfBatch) throws Exception { //Thread.sleep(1000); System.out.println("发送消息成功,当前线程="+Thread.currentThread().getId()+" data="+event.getCarLicense()); } }
- EventTranslator的实现,事件发布回调接口,负责将业务参数设置到事件中
-
package com.scgaopan.disruptor.pakingdemo.producer; import com.lmax.disruptor.EventTranslator; import com.scgaopan.disruptor.pakingdemo.MyInParkingDataEvent; /** * Author:scgaopan * Date:18/10/25 * Description: */ public class MyInParkingDataEventTranslator implements EventTranslator<MyInParkingDataEvent> { public void translateTo(MyInParkingDataEvent myInParkingDataEvent, long sequence) { this.generateData(myInParkingDataEvent); } private MyInParkingDataEvent generateData(MyInParkingDataEvent myInParkingDataEvent) { myInParkingDataEvent.setCarLicense("车牌号: 鄂A-" + (int) (Math.random() * 100000)); // 随机生成一个车牌号 // System.out.println("Thread Id " + Thread.currentThread().getId() + " 写完一个event"); return myInParkingDataEvent; } }
- 事件的发布类
-
package com.scgaopan.disruptor.pakingdemo.producer; import com.lmax.disruptor.dsl.Disruptor; import com.scgaopan.disruptor.pakingdemo.DateUtil; import com.scgaopan.disruptor.pakingdemo.MyInParkingDataEvent; import java.util.concurrent.CountDownLatch; /** * Author:scgaopan * Date:18/10/25 * Description: */ public class MyInParkingDataEventPublisher implements Runnable { private CountDownLatch countDownLatch; // 用于监听初始化操作,等初始化执行完毕后,通知主线程继续工作 private Disruptor<MyInParkingDataEvent> disruptor; private static final Integer NUM = 1; // 1,10,100,1000 public MyInParkingDataEventPublisher(CountDownLatch countDownLatch, Disruptor<MyInParkingDataEvent> disruptor) { this.countDownLatch = countDownLatch; this.disruptor = disruptor; } public void run() { MyInParkingDataEventTranslator eventTranslator = new MyInParkingDataEventTranslator(); try { for (int i = 0; i < NUM; i++) { disruptor.publishEvent(eventTranslator); Thread.sleep(1000); // 假设一秒钟进一辆车 System.out.println(DateUtil.getCurrentTime()+ "有一辆车进入..............."); } } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); // 执行完毕后通知 await()方法 System.out.println(NUM + "辆车已经全部进入进入停车场!"); } } }
- main类
-
package com.scgaopan.disruptor.pakingdemo; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.EventHandlerGroup; import com.lmax.disruptor.dsl.ProducerType; import com.scgaopan.disruptor.pakingdemo.handler.MyParkingDataInDbHandler; import com.scgaopan.disruptor.pakingdemo.handler.MyParkingDataSmsHandler; import com.scgaopan.disruptor.pakingdemo.handler.MyParkingDataToKafkaHandler; import com.scgaopan.disruptor.pakingdemo.producer.MyInParkingDataEventPublisher; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Author:scgaopan * Date:18/10/25 * Description: */ public class MyInParkingDataEventMain { public static void main(String[] args) { long beginTime=System.currentTimeMillis(); int bufferSize = 2048; // 2的N次方 try { // 创建消费者线程池,负责处理Disruptor的四个消费者 ExecutorService executor = Executors.newFixedThreadPool(4); // 初始化一个 Disruptor Disruptor<MyInParkingDataEvent> disruptor = new Disruptor<MyInParkingDataEvent>(new EventFactory<MyInParkingDataEvent>() { public MyInParkingDataEvent newInstance() { return new MyInParkingDataEvent(); // Event 初始化工厂 } }, bufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy()); // 使用disruptor创建消费者组 MyParkingDataInDbHandler 和 MyParkingDataToKafkaHandler EventHandlerGroup<MyInParkingDataEvent> handlerGroup = disruptor.handleEventsWith( new MyParkingDataInDbHandler(), new MyParkingDataToKafkaHandler()); // 当上面两个消费者处理(这里是处理结束,而不是通知)结束后再消耗 smsHandler MyParkingDataSmsHandler myParkingDataSmsHandler = new MyParkingDataSmsHandler(); handlerGroup.then(myParkingDataSmsHandler); // 启动Disruptor disruptor.start(); CountDownLatch countDownLatch = new CountDownLatch(1); // 一个生产者线程准备好了就可以通知主线程继续工作了 // 生产者生成数据 executor.submit(new MyInParkingDataEventPublisher(countDownLatch, disruptor)); countDownLatch.await(); // 等待生产者结束 disruptor.shutdown(); executor.shutdown(); } catch (Exception e) { e.printStackTrace(); } System.out.println("总耗时:"+(System.currentTimeMillis()-beginTime)); } }
- 代码地址:https://gitee.com/scgaopan/diruptor-demo.git
Disruptor源码地址: https://github.com/LMAX-Exchange/disruptor