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. hadoop伪分布式平台组件搭建

    第一部分:系统基础配置 系统基础配置中主完成了安装大数据环境之前的基础配置,如防火墙配置和安装MySQL.JDK安装等 第一步:关闭防火墙 Hadoop与其他组件的服务需要通过端口进行通信,防火墙的存 ...

  2. 如何根据角色批量激活SAP Fiori服务

    我们知道Fiori的角色跟ERP的角色是不通用的,即使你的账号有SAP_ALL的权限,但打开Fiori的时候一样是空的一片: 只有给账号加上fiori需要的角色,并激活相关服务才能用fiori app ...

  3. js 的关键字

    1.get / set var test = { _Name: "Limei", _Age: 20, get name() { return this._Name;}, set a ...

  4. 使用 squid 共享 虚拟专用网至局域网

    最近要出差,但是公司代码放在内网环境,平时在公司使用没问题,如果不在公司,就要拨 虚拟网络以下简称:V网. 但是公司给的 V网,并不是那种,直接用系统自带的网络连接,就可以连接上的,需要装一个软件,登 ...

  5. 如何不使用 overflow: hidden 实现 overflow: hidden

    一个很有意思的题目.如何不使用 overflow: hidden 实现 overflow: hidden? CSS 中 overflow 定义当一个元素的内容太大而无法适应块级格式化上下文时候该做什么 ...

  6. Ossec 安装并配置邮件通知

    Ossec 安装并配置邮件通知 目录 Ossec 安装并配置邮件通知 1. 介绍 2. 软硬件环境 3. 安装步骤 3.1 Server 3.2 Agent 3.3 配置邮件通知 4. 参考资料 1. ...

  7. CTFHub - Web(一)

    请求方法: 1.进入页面,提示:HTTP 请求方法, HTTP/1.1协议中共定义了八种方法(也叫动作)来以不同方式操作指定的资源. 2.当前http的请求方式是get请求,当你使用CTFHUB为请求 ...

  8. Android之Xposed

    基础书籍推荐:1.疯狂JAVA讲义:2.疯狂安卓讲义: 逆向分析必须知道他的原理,不然只会用工具,那就直接GG. 谷歌的镜像网站:https://developers.google.com/andro ...

  9. Nacos集成学习入门

    微服务注册中心nacos学习:先尝试使用它,然后撸它源码搞懂它. 在这里整理一下自己之前集成nacos的内容. 我的github地址:https://github.com/mrxiaobai-wen/ ...

  10. Docker相关简介以及使用方法

    Docker: 可以把它看作是一个软件,在这个软件当中呢,还可以安装其他的软件,还可以把软件所需要的环境依赖一起添加进来,这样让开发人员的程序在不同的环境当中都可以流转起来,避免了程序出现" ...