单机最快MQ—Disruptor

2,232 阅读4分钟

单机最快MQ—Disruptor

今天来讲讲我所知道的单机最快的MQ,它叫Disruptor

先来介绍一下Disruptor,从翻译上来看,Disruptor—分裂、瓦解,Disruptor是国外某个金融、股票交易所开发的,2011年获得Duke奖,为成为单机最快的MQ,性能及高,无锁CAS,单机支持高并发

怎么样,心动了没?来来来,让我来带大家学习一下今天的主角—Disruptor

大家可以把Disruptor当做是内存里的高效的队列

Disruptor简介

  • 无锁(CAS)、高并发,使用环形Buffer,直接覆盖(不用清除)旧数据,降低GC频繁,实现了基于事件的生产者消费者模型(观察者模式)
    • 为什么说它是观察者模式呢?因为消费者时刻关注着队列里有没有消息,一旦有新消息产生,消费者线程就会立刻把它消费

环形队列(RingBuffer)

  1. RingBuffer有一个序号sequence,指向下一个可用元素,采用数组实现,没有首尾指针

    • Disruptor要求你对他设置长度的时候,设置成2的n次幂,这样有利于二进制的运算

    首先,它是基于数组实现的,遍历起来要比链表要快 其次不用维护首尾指针,当然他也没有首尾指针,之需要维护一个sequence即可

  2. 当所有位置都放满了,再放下一个时,就会把0号位置覆盖掉 这时就会有小伙伴着急了,怎么能覆盖掉呢,那我数据不就丢失了吗?

那肯定是不会就让他这么轻易滴把这数据覆盖掉滴,当需要覆盖数据时,会执行一个策略,Disruptor给提供多种策略,说说比较常用的

  • BlockingWaitStrategy策略,常见且默认的等待策略,当这个队列里满了,不执行覆盖,而是在外面阻塞等待
  • SleepingWaitStrategy策略,看字面意思,用睡眠来等待,等待中循环调用LockSupport.parkNanos(1)来睡眠
  • YieldingWaitStrategy策略,循环等待sequence增加到合适的值,循环中调用Thread.yieId(),允许其他准备好的线程执行

Disruptor开发步骤

  1. 定义Event—队列中需要处理的元素
  2. 定义Event工厂,用于填充队列
  3. 定义EventHandler(消费者),处理容器中的元素
//定义Event消息(事件)类
public class LongEvent{

    private long value;
    private String name;

    @Override
    public String toString() {
        return "LongEvent{" +
                "value=" + value +
                ", name='" + name + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public long getValue() {
        return value;
    }
    public void setValue(long value) {
        this.value = value;
    }
}
//定义消息(事件)工厂
public class LongEventFactory implements EventFactory<LongEvent> {
    @Override
    public LongEvent newInstance() {
        return new LongEvent();
    }
}
//定义消息(事件)的消费方式
public class LongEventHandler implements EventHandler<LongEvent> {
    @Override
    public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception {
        System.out.println(longEvent.getName()+"-----"+longEvent.getValue());
    }
}
//消息(事件)生产者
public class LongEventProducer {
    private final RingBuffer<LongEvent> ringBuffer;

    public LongEventProducer(RingBuffer<LongEvent> ringBuffer) {
        this.ringBuffer = ringBuffer;
    }
    public void onData(long val, String name) {
        long sequence = ringBuffer.next();
        try {
            LongEvent event = ringBuffer.get(sequence);
            event.setValue(val);
            event.setName(name);
        } finally {
            ringBuffer.publish(sequence);
        }
    }
}
public static void main(String[] args) {
        //new一个消息(事件)工厂
        LongEventFactory factory = new LongEventFactory();
        //设置环形Buffer的SIZE
        int size = 1024;
        //new Disruptor,参数是消息(事件)工厂,Buffer的Size,线程工厂
        Disruptor<LongEvent> longEventDisruptor = new Disruptor<LongEvent>(factory, size, Executors.defaultThreadFactory());
        //设置如何消费生产者产出的消息(事件)
        longEventDisruptor.handleEventsWith(new LongEventHandler());
        //启动--环形Buffer创建成功,所有的位置均已创建好Event对象
        longEventDisruptor.start();
        //获取Disruptor的环形Buffer
        RingBuffer<LongEvent> ringBuffer = longEventDisruptor.getRingBuffer();
        //new 消息(事件)生产者
        LongEventProducer producer = new LongEventProducer(ringBuffer);
    	//循环调用-往里添加消息
        for(long l = 0; l<100; l++) {
            //TODO   调用producer的生产消息(事件)的方法
            producer.onData(l,"MingLog-"+l);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //将消息(事件)发布出去
        longEventDisruptor.shutdown();
    }

回过头来看看,为什么Disruptor这么快呢?

  1. 底层是数组,循环起来要比链表快
  2. 没有首尾指针,免去了维护两个指针的时间
  3. start()方法被调用,Disruptor被初始化,所有可用空间上的Event全部被初始化(提前创建好,每次进来在原对象上进行修改,不用重新new,不用创建新的对象,也就可以降低GC的频率),因为是一开始就把所有的Event初始化好的,所以next获取下一个可用的Event时就不需要再去判断该Event是否被初始化,减少了一步判断
  4. Disruptor的Size是2的n次幂,方便进行二进制位运算,来确定消息应该放在那个可用区域

好了,Disruptor讲解到这里就结束了,大家有什么想要学习的都可以私信或评论告诉我哦~ 我会尽全力满足大家滴,我学,你也学,咳咳~广告看多了

点赞、关注来一波好吗,秋梨膏~