高并发必备,单机强力,高性能队列制造者:Disruptor 快速入门笔记

网页右边,向下滑有目录索引,可以根据标题跳转到你想看的内容
如果右边没有就找找左边
在马士兵教育笔记基础上更改

作者:马士兵 http://www.mashibing.com
最近更新:2019年10月22日

1、介绍

主页:http://lmax-exchange.github.io/disruptor/

源码:https://github.com/LMAX-Exchange/disruptor

GettingStarted: https://github.com/LMAX-Exchange/disruptor/wiki/Getting-Started

api: http://lmax-exchange.github.io/disruptor/docs/index.html

maven: https://mvnrepository.com/artifact/com.lmax/disruptor

Disruption:分裂,瓦解(表示非常快)
简单来说,就是内存里面的一个生产各种高效队列的工具
据说一个线程1秒可以处理600万订单
2011年获得Duke将(框架的奖项)
速度最快的MQ(消息队列)
性能极高,无锁CAS,单机支持高并发
个人总结
此框架,可以根据不同条件生产高效队列
生产高效队列时,需要先指定,Event工厂,队列大小,Event消费者
Event工厂:每个队列都存储一样的元素,这些元素称为事件Event,工厂用来生成对象
Event消费者:规定队列中元素如何消费
生产队列时,先根据队列大小创建空间,然后根据Event工厂,为每个元素空间赋值,这样就有了一个固定大小,已经填满特定Event的队列了
之后,如果有元素插入,不需要new对象,如果队列满了,新来的元素要插入,也不需要new对象,直接覆盖就可以了
如何确定插入位置:通过一个位置指针,按公式num&(size-1)计算插入位置

2、Disruptor的特点

对比ConcurrentLinkedQueue : Concurrent表示高并发的,Linked表示它是链表实现的
JDK中没有ConcurrentArrayQueue:也就是说没有数组的高并发队列,但是遍历的时候,数组一定比链表快
为什么JDK不加数组实现的队列呢?因为一旦数组满了,想要添加新元素,必须拷贝一份,添加完再建立引用,这样的效率极低
Disruptor就是数组实现的,但是它解决数组达到上限需要拷贝的问题,转而将其想象成环形结构,并维护一个位置指针,一旦下一个指针指向位置超过最大,就执行算法,重新执行第一个位置,然后新插入的值,覆盖当前位置
无锁,高并发,使用环形Buffer,直接覆盖(不用清除)旧的数据,降低GC频率
实现了基于事件的生产者消费者模式(观察者模式)

3、RingBuffer

环形队列
RingBuffer的序号,指向下一个可用的元素
采用数组实现,没有首尾指针
对比ConcurrentLinkedQueue,用数组实现的速度更快

假如长度为8,当添加到第12个元素的时候在哪个序号上呢?用12%8决定
当Buffer被填满的时候到底是覆盖还是等待,由Producer(等待策略)决定
强制长度应该设为2的n次幂,利于二进制计算,例如:12%8 = 12 & (8 - 1) 公式就是 pos = num元素序号 & (size最大容量 -1)

那么如果长度达到了,位置重新指向初始位置,此时发现初始位置的元素,没有被消费,那么新来的元素是插入还是等待,就需要用等待策略,Disruptor自带一共8种等待策略

4、Disruptor开发步骤

  1. 定义Event(事件) - 队列中需要处理的元素,队列中存的都是一个一个的Event对象,一个一个事件
    在这里插入图片描述
//Event事件
public class LongEvent {
    private long value;

    public long getValue() {
        return value;
    }

    public void setValue(long value) {
        this.value = value;
    }
}
  1. 定义Event工厂,用于填充队列,Event的创建需要用到工厂
    这里牵扯到效率问题:disruptor初始化的时候,会调用Event工厂,对ringBuffer进行内存的提前分配(就是看到工厂创建的是指定的Event对象,那么容器就提前分配好对象,),之后插入数据,不用频繁new对象,而是覆盖的方式插入
    GC频率会降低,提高性能
    在这里插入图片描述
import com.lmax.disruptor.EventFactory;

//使用工厂创建Event事件,需要实现EventFactory继承来的接口
public class LongEventFactory implements EventFactory<LongEvent> {
	@Override
    public LongEvent newInstance() {
        return new LongEvent();
    }
}

  1. 定义EventHandler(消费者),处理容器中的元素,我们需要指定消费者如何消费队列中的Event事件
    在这里插入图片描述
    其实这些东西都是函数式编程,可以通过lambda来用比如,创建消费者时可以直接
    disruptor.handleEventsWith((longEvent,l,b)->{System.out.println(longEvent.getValue());});
    在这里插入图片描述
import com.lmax.disruptor.EventHandler;
//Event消费者,继承EventHandler,实现onEvent方法
public class LongEventHandler implements EventHandler<LongEvent> {
	/**
     *
     * @param longEvent 消费哪个小学
     * @param l 哪个位置的消息
     * @param b 是否是最后一个元素
     * @throws Exception
     */
    @Override
    public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception {
        System.out.println(longEvent.getValue());
    }
}
  1. 定义EventProducer(等待策略),前面我们说过,队列满了,一个元素要插入是覆盖还是等待可以自定义,或者使用它自己提供的9种
    在这里插入图片描述
import com.lmax.disruptor.RingBuffer;

import java.nio.ByteBuffer;

public class LongEventProducer {
    private final RingBuffer<LongEvent> ringBuffer;

    public LongEventProducer(RingBuffer<LongEvent> ringBuffer) {
        this.ringBuffer = ringBuffer;
    }

    public void onData(ByteBuffer buffer) {
        long sequence = ringBuffer.next();
        try {
            LongEvent event = ringBuffer.get(sequence);
            event.setValue(buffer.getLong(0));
        } finally {
            ringBuffer.publish(sequence);
        }
    }

}
  1. 编写测试类
    在这里插入图片描述
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.EventTranslator;
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.sun.org.apache.xpath.internal.functions.Function3Args;

import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        //Executor executor = Executors.newCachedThreadPool();

        //创建工厂对象
        LongEventFactory factory = new LongEventFactory();

        //指定环形队列大小,必须是2的倍数,2的n次幂
        int ringBufferSize = 1024;

        //创建Disruption对象,传入工厂对象,队列大小,以及线程工厂(当它要产生消费者时,消费者需要一个线程执行,此参数规定生产什么样的线程)
        Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(factory, ringBufferSize, Executors.defaultThreadFactory(),ProducerType.SINGLE,new BlockingWaitStrategy());

        //拿到事件后,如何进行处理,传入Event消费者对象,规定,消费者拿到事件后如何消费
        disruptor.handleEventsWith(new LongEventHandler());
//        disruptor.handleEventsWith((longEvent,l,b)->{System.out.println(longEvent.getValue());});//lambda表达式形式,不用new那么多类

        //队列开始,内存中一个环形队列生产完成,每个元素都已经根据工厂生产完成
        disruptor.start();

        //获取生产好的队列
        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();

        //官方案例,这样写不能用lambda
        System.out.println("==================普通的发布事件,让消费者来消费====================");
        long sequence = ringBuffer.next();//获取下一个位置
        try{
            LongEvent longEvent = ringBuffer.get(sequence);//获取指定位置元素,为其设置值等等
            longEvent.setValue(8888L);//获取元素后,便可以对其操作
        }finally{
            ringBuffer.publish(sequence);//将这个位置的元素,事件发布出去,让消费者可以获取,获取后会执行消费者的逻辑
        }
        System.out.println("==================通过EventTranslator来发布事件,让消费者来消费====================");
        //我们可以通过定义eventTranslator来实现上面操作,并且方便很多,这个是写死了值的
        EventTranslator<LongEvent> eventTranslator = new EventTranslator<LongEvent>() {
            @Override
            public void translateTo(LongEvent event, long sequence) {
                event.setValue(8888L);
            }
        };
        ringBuffer.publishEvent(eventTranslator);//发布

        //下面这个可以指定一个参数
        EventTranslatorOneArg<LongEvent, Long> eventTranslatorOneArg = new EventTranslatorOneArg<LongEvent, Long>() {

            @Override
            public void translateTo(LongEvent event, long sequence, Long arg0) {
                event.setValue(arg0);
            }
        };
        ringBuffer.publishEvent(eventTranslatorOneArg,10L);//发布

        //可以直接用lambda指定发布
        System.out.println("==================lambda发布事件,让消费者来消费(注意lambda如何指定参数)====================");

        ringBuffer.publishEvent((e,s)->{e.setValue(8889L);});//发布
        ringBuffer.publishEvent((e,s,l)->e.setValue(l),11L);//发布
        ringBuffer.publishEvent((e,s,l1,l2)->e.setValue(l1+l2),11L,22L);//发布

        //获取等待策略
        LongEventProducer producer = new LongEventProducer(ringBuffer);


        //字节缓存
        ByteBuffer bb = ByteBuffer.allocate(8);

        for(long l = 0; l<100; l++) {
            bb.putLong(0, l);

            producer.onData(bb);

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        disruptor.shutdown();//关闭Disruptor

//
    }
}

5、事件发布模板

long sequence = ringBuffer.next();  // Grab the next sequence
try {
    LongEvent event = ringBuffer.get(sequence); // Get the entry in the Disruptor
    // for the sequence
    event.set(8888L);  // Fill with data
} finally {
    ringBuffer.publish(sequence);
}

6、使用EventTranslator发布事件

System.out.println("==================通过EventTranslator来发布事件,让消费者来消费====================");
        //我们可以通过定义eventTranslator来实现上面操作,并且方便很多,这个是写死了值的
        EventTranslator<LongEvent> eventTranslator = new EventTranslator<LongEvent>() {
            @Override
            public void translateTo(LongEvent event, long sequence) {
                event.setValue(8888L);
            }
        };
        ringBuffer.publishEvent(eventTranslator);//发布

        //下面这个可以指定一个参数
        EventTranslatorOneArg<LongEvent, Long> eventTranslatorOneArg = new EventTranslatorOneArg<LongEvent, Long>() {

            @Override
            public void translateTo(LongEvent event, long sequence, Long arg0) {
                event.setValue(arg0);
            }
        };
        ringBuffer.publishEvent(eventTranslatorOneArg,10L);//发布
        
        //===============================================================
        EventTranslatorTwoArg<LongEvent, Long, Long> translator3 = new EventTranslatorTwoArg<LongEvent, Long, Long>() {
            @Override
            public void translateTo(LongEvent event, long sequence, Long l1, Long l2) {
                event.set(l1 + l2);
            }
        };

        ringBuffer.publishEvent(translator3, 10000L, 10000L);

        //===============================================================
        EventTranslatorThreeArg<LongEvent, Long, Long, Long> translator4 = new EventTranslatorThreeArg<LongEvent, Long, Long, Long>() {
            @Override
            public void translateTo(LongEvent event, long sequence, Long l1, Long l2, Long l3) {
                event.set(l1 + l2 + l3);
            }
        };

        ringBuffer.publishEvent(translator4, 10000L, 10000L, 1000L);

        //===============================================================
        EventTranslatorVararg<LongEvent> translator5 = new EventTranslatorVararg<LongEvent>() {

            @Override
            public void translateTo(LongEvent event, long sequence, Object... objects) {
                long result = 0;
                for(Object o : objects) {
                    long l = (Long)o;
                    result += l;
                }
                event.set(result);
            }
        };

        ringBuffer.publishEvent(translator5, 10000L, 10000L, 10000L, 10000L);

7、使用Lamda表达式

public class Main03
{
    public static void main(String[] args) throws Exception
    {
        // Specify the size of the ring buffer, must be power of 2.
        int bufferSize = 1024;

        // Construct the Disruptor
        Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);

        // Connect the handler
        disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event));

        // 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();


        //可以直接用lambda指定发布
        System.out.println("==================lambda发布事件,让消费者来消费(注意lambda如何指定参数)====================");

        ringBuffer.publishEvent((e,s)->{e.setValue(8889L);});//发布
        ringBuffer.publishEvent((e,s,l)->e.setValue(l),11L);//发布
        ringBuffer.publishEvent((e,s,l1,l2)->e.setValue(l1+l2),11L,22L);//发布
    }
}

8、ProducerType生产者线程模式

//创建Disruption对象,传入工厂对象,队列大小,以及线程工厂,指定生产者线程模式(指定它必须在后面也指定等待策略)
Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(factory, ringBufferSize, Executors.defaultThreadFactory(), ProducerType.SINGLE,,new BlockingWaitStrategy());

ProducerType有两种模式 Producer.MULTI和Producer.SINGLE

默认是MULTI,表示在多线程模式下产生sequence

如果确认是单线程生产者,那么可以指定SINGLE,效率会提升

如果是多个生产者(多线程),但模式指定为SINGLE,会出什么问题呢?

9、等待策略

1,(常用)BlockingWaitStrategy:通过线程阻塞的方式,等待生产者唤醒,被唤醒后,再循环检查依赖的sequence是否已经消费。

2,BusySpinWaitStrategy:线程一直自旋等待,可能比较耗cpu

3,LiteBlockingWaitStrategy:线程阻塞等待生产者唤醒,与BlockingWaitStrategy相比,区别在 signalNeeded.getAndSet,如果两个线程同时访问一个访问waitfor,一个访问signalAll时,可以减少lock加锁次数.

4,LiteTimeoutBlockingWaitStrategy:与LiteBlockingWaitStrategy相比,设置了阻塞时间,超过时间后抛异常。

5,PhasedBackoffWaitStrategy:根据时间参数和传入的等待策略来决定使用哪种等待策略

6,TimeoutBlockingWaitStrategy:相对于BlockingWaitStrategy来说,设置了等待时间,超过后抛异常

7,(常用)YieldingWaitStrategy:尝试100次,然后Thread.yield()让出cpu

8,(常用)SleepingWaitStrategy : sleep

10、 消费者异常处理

默认:disruptor.setDefaultExceptionHandler()

覆盖:disruptor.handleExceptionFor().with()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殷丿grd_志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值