Disruptor入门

 

获得Disruptor

可以通过Maven或者下载jar来安装Disruptor。只要把对应的jar放在Java classpath就可以了。

基本的事件生产和消费

我们从一个简单的例子开始学习Disruptor:生产者传递一个long类型的值给消费者,而消费者消费这个数据的方式仅仅是把它打印出来。首先声明一个Event来包含需要传递的数据:

 1 public class LongEvent {
2 private long value;
3 public long getValue() {
4 return value;
5 }
6
7 public void setValue(long value) {
8 this.value = value;
9 }
10 }

由于需要让Disruptor为我们创建事件,我们同时还声明了一个EventFactory来实例化Event对象。

1 public class LongEventFactory implements EventFactory {
2 @Override
3 public Object newInstance() {
4 return new LongEvent();
5 }
6 }

我们还需要一个事件消费者,也就是一个事件处理器。这个事件处理器简单地把事件中存储的数据打印到终端:

1 /**
2 */public class LongEventHandler implements EventHandler<LongEvent> {
3 @Override
4 public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception {
5 System.out.println(longEvent.getValue());
6 }
7 }

事件都会有一个生成事件的源,这个例子中假设事件是由于磁盘IO或者network读取数据的时候触发的,事件源使用一个ByteBuffer来模拟它接受到的数据,也就是说,事件源会在IO读取到一部分数据的时候触发事件(触发事件不是自动的,程序员需要在读取到数据的时候自己触发事件并发布):

 1 public class LongEventProducer {
2 private final RingBuffer<LongEvent> ringBuffer;
3 public LongEventProducer(RingBuffer<LongEvent> ringBuffer) {
4 this.ringBuffer = ringBuffer;
5 }
6
7 /**
8 * onData用来发布事件,每调用一次就发布一次事件事件
9 * 它的参数会通过事件传递给消费者
10 *
11 * @param bb
12 */public void onData(ByteBuffer bb) {
13 //可以把ringBuffer看做一个事件队列,那么next就是得到下面一个事件槽
14 long sequence = ringBuffer.next();try {
15 //用上面的索引取出一个空的事件用于填充
16 LongEvent event = ringBuffer.get(sequence);// for the sequence
17 event.setValue(bb.getLong(0));
18 } finally {
19 //发布事件
20 ringBuffer.publish(sequence);
21 }
22 }
23 }

很明显的是:当用一个简单队列来发布事件的时候会牵涉更多的细节,这是因为事件对象还需要预先创建。发布事件最少需要两步:获取下一个事件槽并发布事件(发布事件的时候要使用try/finnally保证事件一定会被发布)。如果我们使用RingBuffer.next()获取一个事件槽,那么一定要发布对应的事件。如果不能发布事件,那么就会引起Disruptor状态的混乱。尤其是在多个事件生产者的情况下会导致事件消费者失速,从而不得不重启应用才能会恢复。

Disruptor 3.0提供了lambda式的API。这样可以把一些复杂的操作放在Ring Buffer,所以在Disruptor3.0以后的版本最好使用Event Publisher或者Event Translator来发布事件。

 1 public class LongEventProducerWithTranslator {
2 //一个translator可以看做一个事件初始化器,publicEvent方法会调用它
3 //填充Event
4 private static final EventTranslatorOneArg<LongEvent, ByteBuffer> TRANSLATOR =
5 new EventTranslatorOneArg<LongEvent, ByteBuffer>() {
6 public void translateTo(LongEvent event, long sequence, ByteBuffer bb) {
7 event.setValue(bb.getLong(0));
8 }
9 };
10 private final RingBuffer<LongEvent> ringBuffer;
11 public LongEventProducerWithTranslator(RingBuffer<LongEvent> ringBuffer) {
12 this.ringBuffer = ringBuffer;
13 }
14
15 public void onData(ByteBuffer bb) {
16 ringBuffer.publishEvent(TRANSLATOR, bb);
17 }
18 }

上面写法的另一个好处是,Translator可以分离出来并且更加容易单元测试。Disruptor提供了不同的接口(EventTranslator, EventTranslatorOneArg, EventTranslatorTwoArg, 等等)去产生一个Translator对象。很明显,Translator中方法的参数是通过RingBuffer来传递的。

最后一步就是把所有的代码组合起来完成一个完整的事件处理系统。Disruptor在这方面做了简化,使用了DSL风格的代码(其实就是按照直观的写法,不太能算得上真正的DSL)。虽然DSL的写法比较简单,但是并没有提供所有的选项。如果依靠DSL已经可以处理大部分情况了。

public class LongEventMain {
public static void main(String[] args) throws InterruptedException {
// Executor that will be used to construct new threads for consumers
Executor executor = Executors.newCachedThreadPool();
// The factory for the event
LongEventFactory factory = new LongEventFactory();
// Specify the size of the ring buffer, must be power of 2.
int bufferSize = 1024;
// Construct the Disruptor
Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(factory, bufferSize, executor);
// Connect the handler
disruptor.handleEventsWith(new LongEventHandler());
// Start the Disruptor, starts all threads running
disruptor.start();
// Get the ring buffer from the Disruptor to be used for publishing.
RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer(); LongEventProducer producer = new LongEventProducer(ringBuffer); ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++) {
bb.putLong(0, l);
producer.onData(bb);
Thread.sleep(1000);
}
}
}

使用Java 8

Disruptor在自己的接口里面添加了对于Java 8 Lambda的支持。大部分Disruptor中的接口都符合Functional Interface的要求(也就是在接口中仅仅有一个方法)。所以在Disruptor中,可以广泛使用Lambda来代替自定义类。

 1 public class LongEventMainJava8 {
2 /**
3 * 用lambda表达式来注册EventHandler和EventProductor
4 * @param args
5 * @throws InterruptedException
6 */public static void main(String[] args) throws InterruptedException {
7 // Executor that will be used to construct new threads for consumers
8 Executor executor = Executors.newCachedThreadPool();
9 // Specify the size of the ring buffer, must be power of 2.
10 int bufferSize = 1024;// Construct the Disruptor
11 Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, executor);
12 // 可以使用lambda来注册一个EventHandler
13 disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event.getValue()));
14 // Start the Disruptor, starts all threads running
15 disruptor.start();
16 // Get the ring buffer from the Disruptor to be used for publishing.
17 RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
18
19 LongEventProducer producer = new LongEventProducer(ringBuffer);
20
21 ByteBuffer bb = ByteBuffer.allocate(8);for (long l = 0; true; l++) {
22 bb.putLong(0, l);
23 ringBuffer.publishEvent((event, sequence, buffer) -> event.setValue(buffer.getLong(0)), bb);
24 Thread.sleep(1000);
25 }
26 }
27 }

在上面的代码中,有很多自定义类型可以被省略了。还有注意的是:publishEvent方法中仅调用传递给它的参数,并不是直接调用对应的对象。如果把这段代码换成下面的代码:

1 ByteBuffer bb = ByteBuffer.allocate(8);
2 for (long l = 0; true; l++)
3 {
4 bb.putLong(0, l);
5 ringBuffer.publishEvent((event, sequence) -> event.set(bb.getLong(0)));
6 Thread.sleep(1000);
7 }

这段代码中有一个捕获参数的lambda,意味着在lambda表达式生成的内部类中会生成一个对象来存储这个捕获的bb对象。这会增加不必要的GC。所以在需要较低GC水平的情况下最好把所有的参数都通过publishEvent传递。

由于在Java 8中方法引用也是一个lambda,因此还可以把上面的代码改成下面的代码:

 1 public class LongEventWithMethodRef {
2 public static void handleEvent(LongEvent event, long sequence, boolean endOfBatch)
3 {
4 System.out.println(event.getValue());
5 }
6
7 public static void translate(LongEvent event, long sequence, ByteBuffer buffer)
8 {
9 event.setValue(buffer.getLong(0));
10 }
11
12 public static void main(String[] args) throws Exception
13 {
14 // Executor that will be used to construct new threads for consumers
15 Executor executor = Executors.newCachedThreadPool();
16 // Specify the size of the ring buffer, must be power of 2.
17 int bufferSize = 1024;
18 // Construct the Disruptor
19 Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, executor);
20 // Connect the handler
21 disruptor.handleEventsWith(LongEventWithMethodRef::handleEvent);
22 // Start the Disruptor, starts all threads running
23 disruptor.start();
24 // Get the ring buffer from the Disruptor to be used for publishing.
25 RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
26
27 LongEventProducer producer = new LongEventProducer(ringBuffer);
28
29 ByteBuffer bb = ByteBuffer.allocate(8);
30 for (long l = 0; true; l++)
31 {
32 bb.putLong(0, l);
33 ringBuffer.publishEvent(LongEventWithMethodRef::translate, bb);
34 Thread.sleep(1000);
35 }
36 }
37 }
38

基本调整选项

上面的代码已经可以处理大多数的情况了,但是在有的时候还是会需要根据不同的软件或者硬件来调整选项以获得更高的性能。基本的选项有两个:单或者多生产者模式和可选的等待策略。

单或多 事件生产者

在并发系统中提高性能最好的方式之一就是单一写者原则,对Disruptor也是适用的。如果在你的代码中仅仅有一个事件生产者,那么可以设置为单一生产者模式来提高系统的性能。

 1 public class singleProductorLongEventMain {
2 public static void main(String[] args) throws Exception {
3 //.....// Construct the Disruptor with a SingleProducerSequencer
4
5 Disruptor<LongEvent> disruptor = new Disruptor(factory,
6 bufferSize,
7 ProducerType.SINGLE, // Single producernew BlockingWaitStrategy(),
8 executor);//.....
9 }
10 }

为了证明,下面的数据是从Mac Air i7上面测试的结果:

多生产者:

Run 0, Disruptor=26,553,372 ops/sec
Run 1, Disruptor=28,727,377 ops/sec
Run 2, Disruptor=29,806,259 ops/sec
Run 3, Disruptor=29,717,682 ops/sec
Run 4, Disruptor=28,818,443 ops/sec
Run 5, Disruptor=29,103,608 ops/sec
Run 6, Disruptor=29,239,766 ops/sec

单生产者:

Run 0, Disruptor=89,365,504 ops/sec
Run 1, Disruptor=77,579,519 ops/sec
Run 2, Disruptor=78,678,206 ops/sec
Run 3, Disruptor=80,840,743 ops/sec
Run 4, Disruptor=81,037,277 ops/sec
Run 5, Disruptor=81,168,831 ops/sec
Run 6, Disruptor=81,699,346 ops/sec

 可选的等待策略

Disruptor默认的等待策略是BlockingWaitStrategy。这个策略的内部适用一个锁和条件变量来控制线程的执行和等待(Java基本的同步方法)。BlockingWaitStrategy是最慢的等待策略,但也是CPU使用率最低和最稳定的选项。然而,可以根据不同的部署环境调整选项以提高性能。

SleepingWaitStrategy

和BlockingWaitStrategy一样,SpleepingWaitStrategy的CPU使用率也比较低。它的方式是循环等待并且在循环中间调用LockSupport.parkNanos(1)来睡眠,(在Linux系统上面睡眠时间60µs).然而,它的优点在于生产线程只需要计数,而不执行任何指令。并且没有条件变量的消耗。但是,事件对象从生产者到消费者传递的延迟变大了。SleepingWaitStrategy最好用在不需要低延迟,而且事件发布对于生产者的影响比较小的情况下。比如异步日志功能。

YieldingWaitStrategy

YieldingWaitStrategy是可以被用在低延迟系统中的两个策略之一,这种策略在减低系统延迟的同时也会增加CPU运算量。YieldingWaitStrategy策略会循环等待sequence增加到合适的值。循环中调用Thread.yield()允许其他准备好的线程执行。如果需要高性能而且事件消费者线程比逻辑内核少的时候,推荐使用YieldingWaitStrategy策略。例如:在开启超线程的时候。

BusySpinWaitStrategy

BusySpinWaitStrategy是性能最高的等待策略,同时也是对部署环境要求最高的策略。这个性能最好用在事件处理线程比物理内核数目还要小的时候。例如:在禁用超线程技术的时候。

转载自并发编程网 – ifeve.com本文链接地址: Disruptor入门

通过工作池简单处理  WorkerPool:

        

 1 //Trade:交易实体类  
2
3         /**
4         *
5         *类描述:交易实例类
6         *@author: 豪
7         *@date: 日期:2017-5-17 时间:下午4:51:26
8         *@version 1.0
9         */
10           public class Trade {
11
12           private String id;//ID
13           private String name;
14           private double price;//金额
15           private AtomicInteger count = new AtomicInteger(0);
16
17           public String getId() {
18           return id;
19           }
20           public void setId(String id) {
21             this.id = id;
22             }
23           public String getName() {
24             return name;
25             }
26           public void setName(String name) {
27             this.name = name;
28             }
29           public double getPrice() {
30             return price;
31             }
32           public void setPrice(double price) {
33             this.price = price;
34             }
35           public AtomicInteger getCount() {
36             return count;
37             }
38           public void setCount(AtomicInteger count) {
39             this.count = count;
40             }
41           }
42
43   

     

 1 // TradeHandler:交易实体类 消费者处理器
2
3         
4
5        /**
6         *
7         *类描述:消费者管理器
8         *@author: 豪
9         *@date: 日期:2017-5-17 时间:下午5:56:12
10         *@version 1.0
11         */
12         public class TradeHandler implements WorkHandler<Trade> {
13
14         
15
16           @Override
17           public void onEvent(Trade event) throws Exception {
18           //这里做具体的消费逻辑
19           event.setId(UUID.randomUUID().toString());//简单生成下ID
20           System.out.println(event.getId());
21           }
22         }
23
24

     

 1  //main测试类:
2
3         
4
5         public class Main {
6           public static void main(String[] args) throws InterruptedException {
7           int BUFFER_SIZE=1024; //定义缓冲池大小 注意一定是2的N次方
8           int THREAD_NUMBERS=4; //定义线程池的大小
9
10           //创建一个消费类的Factory工厂
11           EventFactory<Trade> eventFactory = new EventFactory<Trade>() {
12             public Trade newInstance() {
13             return new Trade();
14             }
15            };
16
17         //创建一个缓冲池 参数包含消费者工厂 和指定缓冲池大小
18         RingBuffer<Trade> ringBuffer = RingBuffer.createSingleProducer(eventFactory, BUFFER_SIZE);
19
20         //创建SequenceBarrier 平衡方式
21         SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();
22
23         //创建一个有界的线程池
24         ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUMBERS);
25
26         //实例化一个消费者 其必须实现 implements WorkHandler 的方法 。方法里面写具体的业务实现
27         WorkHandler<Trade> handler = new TradeHandler();
28
29
30
31         //创建一个工作池,把刚才实例化消费者的放在里面 参数包括缓冲池 sequenceBarrier平衡类 异常处理类 消费者
32         WorkerPool<Trade> workerPool = new WorkerPool<Trade>(ringBuffer, sequenceBarrier, new IgnoreExceptionHandler(), handler);
33
34         workerPool.start(executor);
35
36           //下面这个生产8个数据
37           for(int i=0;i<8;i++){
38           long seq=ringBuffer.next(); //获取到下一个空对象的位置下标
39           ringBuffer.get(seq).setPrice(Math.random()*9999); //设置缓冲池具体的消费数据
40           ringBuffer.publish(seq); //发布消费者
41           }
42
43         Thread.sleep(1000);
44         workerPool.halt(); //工作池停止
45         executor.shutdown(); //线程池关闭
46         }
47       }
48
49
50
51       

      

        

      

java架构《并发编程框架篇 __Disruptor》的更多相关文章

  1. java架构《并发线程高级篇四》

    本章主要讲并发线程的常见的两种锁.重入锁和读写锁 一:重入锁(ReentrantLock) 概念:重入锁,在需要进行同步的代码加锁,但最后一定不要忘记释放锁,否则会造成锁永远不能释放,其他线程进不了 ...

  2. java架构《并发线程高级篇一》

    本章主要记录讲解并发线程的线程池.java.util.concurrent工具包里面的工具类. 一:Executor框架: Executors创建线程池的方法: newFixedThreadPool( ...

  3. java架构《并发线程高级篇二》

    本章主要记录讲解并发线程的线程池.使用Executor框架自定义线程池. 自定义线程池使用Queue队列所表示出来的形式: 1 ArrayBlockingQueue<Runnable>(3 ...

  4. java架构《并发线程高级篇三》

    本章主要介绍和讲解concurrent.util里面的常用的工具类. 一.CountDownLatch使用:(用于阻塞主线程) 应用场景 :通知线程休眠和运行的工具类,是wait和notify的升级版 ...

  5. Java高并发 -- 线程池

    Java高并发 -- 线程池 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 在使用线程池后,创建线程变成了从线程池里获得空闲线程,关闭线程变成了将线程归坏给线程池. ...

  6. Java高并发--线程安全策略

    Java高并发--线程安全策略 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 不可变对象 发布不可变对象可保证线程安全. 实现不可变对象有哪些要注意的地方?比如JDK ...

  7. Java并发-线程池篇-附场景分析

    作者:汤圆 个人博客:javalover.cc 前言 前面我们在创建线程时,都是直接new Thread(): 这样短期来看是没有问题的,但是一旦业务量增长,线程数过多,就有可能导致内存异常OOM,C ...

  8. Java高并发与多线程(四)-----锁

    今天,我们开始Java高并发与多线程的第四篇,锁. 之前的三篇,基本上都是在讲一些概念性和基础性的东西,东西有点零碎,但是像文科科目一样,记住就好了. 但是本篇是高并发里面真正的基石,需要大量的理解和 ...

  9. Java之创建线程的方式四:使用线程池

    import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.c ...

  10. java架构《并发线程中级篇》

    java多线程的三大设计模式 本章主要记录java常见的三大设计模式,Future.Master-Worker和生产者-消费者模式. 一.Future模式 使用场景:数据可以不及时返回,到下一次实际要 ...

随机推荐

  1. WebService 适用场合

    适用场合 1.跨防火墙通信 如果应用程序有成千上万的用户,而且分布在世界各地,那么客户端和服务器之间的通信将是一个棘手的问题.因为客户端和服务器之间通常会有防火墙或者代理服 务器.在这种情况下,使用D ...

  2. 浅谈java中异常处理

    java语言的异常捕获结构是由try.catch.finally,try中语句块是可能发生异常的java语句,catch用来激发捕获的异常,try语句块中如果发生异常,则调到catch语句块中执行ca ...

  3. hive中一般取top n时,row_number(),rank,dense_ran()常用三个函数

    一. 分区函数Partition By与row_number().rank().dense_rank()的用法(获取分组(分区)中前几条记录) 一.数据准备 --1.创建学生成绩表 id int,   ...

  4. Jetbrains系列产品重置试用方法

    0x0. 项目背景 Jetbrains家的产品有一个很良心的地方,他会允许你试用30天(这个数字写死在代码里了)以评估是否你真的需要为它而付费.但很多时候会出现一种情况:IDE并不能按照我们实际的试用 ...

  5. 【JDBC核心】操作 BLOB 类型字段

    操作 BLOB 类型字段 MySQL BLOB 类型 MySQL 中,BLOB 是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据. 插入 BLOB 类型的数据必须使用 Pre ...

  6. python3.6安装教程

    Python代码要运行,必须要有Python解释器.Python3.x的版本是没有什么区别的,这里以3.6版本来演示安装的过程.这里只介绍Windows环境下的安装. 下载安装程序 Python官方的 ...

  7. Java并发包源码学习系列:详解Condition条件队列、signal和await

    目录 Condition接口 AQS条件变量的支持之ConditionObject内部类 回顾AQS中的Node void await() 添加到条件队列 Node addConditionWaite ...

  8. 日常采坑:.NetCore上传大文件

    一..NetCore上传大文件 .NetCore3.1 webapi 本地测试上传时,遇到一个坑,大点的文件直接失败,根本不走控制器方法. 二.大文件上传配置 IFormFile方式,vs IIS E ...

  9. 如何构建一个多人(.io) Web 游戏,第 1 部分

    原文:How to Build a Multiplayer (.io) Web Game, Part 1 GitHub: https://github.com/vzhou842/example-.io ...

  10. 【Spring】Spring中的Bean - 1、Baen配置

    Bean配置 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis)-Spring中的Bean 什么是Spring中的Bean? Spring可以被看作是一个 ...