disruptor高性能环形队列

原文链接: https://my.oschina.net/u/3795437/blog/2873689

简介

说到Disruptor,首先需要谈谈LMAX。它是欧洲第一家也是唯一一家采用多边交易设施Multilateral Trading Facility(MTF)拥有交易所牌照和经纪商牌照的欧洲顶级金融公司。它们所构建的金融交易平台,建立在JVM平台上, 能够以很低的延迟(latency)产生大量交易(吞吐量)。 这个系统的核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单. 这个业务逻辑处理器完全是运行在内存中(in-memory),使用事件源驱动方式(event sourcing)运行,这个业务逻辑处理器的核心是Disruptor。

从功能上讲Disruptor的功能与JDK中的BlockingQueue相似,但比BlockingQueue更加强大。通过Disruptor可以高效的实现线程间的消息通信。Disruptor主要有以下特点:

  • 基于生产者/消费者模型,但支持多生产者/多消费者、广播、组播等模式。

  • 为事件预先分配内存

  • 支持多种等待策略,并且支持可选的无锁并发算法。

Disruptor的相关概念

Event事件

从生产者传递给消费者的数据单位,可以是任意的java bean。用以表示某一事件,并可存储相关业务参数。该bean由使用者根据自己的需求创建。

EventFactory事件工厂

用来创建的Event事件对象,由使用者实现。需要实现disruptor中的EventFactory接口,Disruptor会通过该工厂来实现为事件预分配内存(即在初始化disruptor时就创建好所有事件对象)的操作。

EventHandle事件消费者

事件的消费者,需要实现Disruptor的EventHandle接口。

WaitStrategy等待策略

所谓等待策略就是Disruptor的消费者在空闲状态下采用何种方式等待任务,不同策略在不同的场景下都有不同的性能表现。在Disruptor中提供了四种等待策略

  • BlockingWaitStrategy 阻塞等待策略

        BlockingWaitStrategy是disruptor的默认消费者等待策略。BlockingWaitStrategy内部使用的是典型的锁机制,来处理消费者线程的唤醒。 这种策略是所有等待策略中最慢的一种,但是也是最保守使用消耗cpu的一种用法,并且在不同的部署环境下最能保持性能一致。

  • SleepingWaitStrategy  休眠等待策略

          像BlockingWaitStrategy一样,SleepingWaitStrategy也是属于一种保守使用cpu的策略。 它使用一个简单的loop繁忙等待循环,但是在循环体中间它调用了LockSupport.parkNanos(1)。 通常在linux系统这样会使得线程停顿大约60微秒。但是这样做的好处是,生产者线程不需要额外的动作去累加计数器,也不需要产生条件变量信号量开销。 但是这样带来的负面影响是,在生产者线程与消费者线程之间传递event的延迟变高了。所以SleepingWaitStrategy适合在不需要低延迟, 但需要很低的生产者线程影响的情形。一个典型的案例就是异步日志记录功能。

  • YieldingWaitStrategy  服从等待策略

         YieldingWaitStrategy是2种可以用于低延迟系统的等待策略之一,充分使用压榨cpu来达到降低延迟的目标。它不断的循环等待事件。 但是,每100次循环会调用一次Thread.yield()让渡CPU执行权来允许其他的排队线程执行。 这是一种在需要极高性能并且event handler线程数少于cpu逻辑内核数的时候推荐使用的策略。

  • BusySpinWaitStrategy  繁忙旋转等待策略

BusySpinWaitStrategy是性能最高的等待策略,他需要消耗大量的CPU资源,它同样是通过不断的循环来等待事件,但与YieldingWaitStrategy相比,它少了让渡CPU执行权的操作,也就是说该等待策略会让消费者线程一直霸占着CPU。

Ring Buffer 环形队列

Disruptor最主要的组件,负责存储和更新事件对象。它是一个2的N次方大小的有界队列,

Disruptor的简单使用

使用disruptor可以分为以下几个主要步骤:

  1. 定义一个Event事件类,用于描述事件,该类为普通的javabean 不需要实现Disruptor的任何接口

  2. 定义一个EventFactory 事件工厂类,需实现Disruptor的EventFactory接口,Disruptor会通过该工厂类预创建事件对象。

  3. 定义一个EventHandle 事件处理类(事件消费者),需实现Disruptor的EventHandle接口。

  4. 创建Distuptor对象,将上述的定义的bean初始化并注册到Disruptor对象中。

  5. 通过Distuptor对象或者RingBuffer对象的publishEvent、publish方法发布事件。(RingBuffer是实际处理事件发布和事件存在的组件)

 

如下是一个简单的单生产者,多消费者的代码示例。

maven配置


    com.lmax
    disruptor
    3.4.2

Event

import lombok.Data;

/**
 * 订单回调事件
 *
 * @version OrderEvent.java, v 0.1 2018-12-25 21:28
 */
@Data
public class OrderCallBackEvent {

   private String orderSn;
}

EventFactory

import com.lmax.disruptor.EventFactory;

/**
 * 订单回调事件工厂
 *
 * @version OrderCallBackFactory.java, v 0.1 2018-12-25 21:30
 */
public class OrderCallBackFactory implements EventFactory {

    @Override
    public OrderCallBackEvent newInstance() {
        // disruptor通过该事件工厂为事件预分配内存空间并创建空的事件对象
        return new OrderCallBackEvent();
    }
}

EventHandler

import com.lmax.disruptor.EventHandler;

/**
 * 订单回调事件消费 - 开放平台通知
 *
 * @version OpenNotifiyHandle.java, v 0.1 2018-12-25 21:53
 */
public class OpenNotifiyHandle implements EventHandler {

    @Override
    public void onEvent(OrderCallBackEvent event, long sequence, boolean endOfBatch) throws Exception {
        String orderSn = event.getOrderSn();
        System.out.println("当前线程:" + Thread.currentThread().getName() + " ,OpenNotifiyHandle == orderSn:" +orderSn + " ,sequence:" + sequence + " ,endOfBatch:" + endOfBatch);
    }
}

 

import com.lmax.disruptor.EventHandler;

/**
 * 订单回调事件消费 - 微信吸粉通知
 *
 * @version WxFansHandle.java, v 0.1 2018-12-25 21:58
 */
public class WxFansHandle implements EventHandler {

    @Override
    public void onEvent(OrderCallBackEvent event, long sequence, boolean endOfBatch) throws Exception {
        String orderSn = event.getOrderSn();
        System.out.println("当前线程:" + Thread.currentThread().getName() + " ,WxFansHandle == orderSn:" +orderSn + " ,sequence:" + sequence + " ,endOfBatch:" + endOfBatch);
    }
}

 

OrderCallBackMain

import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.lmax.disruptor.util.DaemonThreadFactory;

/**
 * 订单回调入口
 *
 * @version OrderCallBackMain.java, v 0.1 2018-12-25 21:39
 */
public class OrderCallBackMain {

    /**
     * ring buffer的大小必须设置为2的指数倍,主要是为了将求模运算转为&运算提高效率
     */
    static int RING_BUFFER_SIZE = 1024;

    public static void main(String[] args) throws InterruptedException {
        // 订单事件工厂,用于预创建订单事件
        OrderCallBackFactory orderCallBackFactory = new OrderCallBackFactory();

        // 初始化disruptor
        // orderCallBackFactory: 事件工厂
        // RING_BUFFER_SIZE: 环形队列的大小,必须是2的指数倍
        // DaemonThreadFactory.INSTANCE: 线程工厂, disruptor会通过线程工程来创建消费者线程,默认每个消费者只会创建一个消费者线程
        // 
        Disruptor disruptor = new Disruptor<>(orderCallBackFactory, RING_BUFFER_SIZE, DaemonThreadFactory.INSTANCE, ProducerType.MULTI, new BlockingWaitStrategy());

        // disruptor默认支持广播模式,即同一个消息,所有注册的消费者都会消费一次
        // 注册开放平台回调处、注册微信吸粉微信吸粉
        disruptor.handleEventsWith(new OpenNotifiyHandle(), new WxFansHandle());

        // 启动disruptor
        disruptor.start();

        // 发布订单事件
        // disruptor会通过我们设置的事件工厂预先帮我创建好事件对象
        // 所以我们在发布事件时只需要为我们的event对象写入数据
        disruptor.publishEvent((event, sequence) -> {
            event.setOrderSn("123456");
        });

        // 发布订单事件
        disruptor.publishEvent((event, sequence) -> {
            event.setOrderSn("9999");
        });
        
        // 另一种发布事件的方式,与上面的方式等价
        RingBuffer ringBuffer = disruptor.getRingBuffer();
        long sequence = ringBuffer.next();
        OrderCallBackEvent event = ringBuffer.get(sequence);
        event.setOrderSn("1234856");
        ringBuffer.publish(sequence);
    }
}

 

转载于:https://my.oschina.net/u/3795437/blog/2873689

你可能感兴趣的:(disruptor高性能环形队列)