美文网首页
Disruptor入门

Disruptor入门

作者: 多喝水JS | 来源:发表于2019-04-12 17:32 被阅读0次

    需求

    生产者传递一个long类型的值给消费者,而消费者消费这个数据的方式仅仅是把它打印出来。

    Event

    声明一个Event来包含需要传递的数据

    public class LongEvent { 
        private long value;
        public long getValue() { 
            return value; 
        } 
     
        public void setValue(long value) { 
            this.value = value; 
        } 
    } 
    

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

    public class LongEventHandler implements EventHandler<LongEvent> { 
        @Override 
        public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception { 
            System.out.println(longEvent.getValue()); 
        } 
    } 
    

    事件生产者

    public class LongEventProducer { 
        private final RingBuffer<LongEvent> ringBuffer;
        public LongEventProducer(RingBuffer<LongEvent> ringBuffer) { 
            this.ringBuffer = ringBuffer; 
        } 
     
        /** 
         * onData用来发布事件,每调用一次就发布一次事件 
         * 它的参数会通过事件传递给消费者 
         * 
         * @param bb 
         */public void onData(ByteBuffer bb) { 
                //可以把ringBuffer看做一个事件队列,那么next就是得到下面一个事件槽
                long sequence = ringBuffer.next();try { 
                //用上面的索引取出一个空的事件用于填充 
                LongEvent event = ringBuffer.get(sequence);// for the sequence 
                event.setValue(bb.getLong(0)); 
            } finally { 
                //发布事件 
                ringBuffer.publish(sequence); 
            } 
        } 
    } 
    

    发布事件最少需要两步:获取下一个事件槽并发布事件(发布事件的时候要使用try/finnally保证事件一定会被发布)。如果我们使用RingBuffer.next()获取一个事件槽,那么一定要发布对应的事件。如果不能发布事件,那么就会引起Disruptor状态的混乱。尤其是在多个事件生产者的情况下会导致事件消费者失速,从而不得不重启应用才能会恢复。

    事件处理系统

    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); 
            } 
        } 
    } 
    

    Disruptor 3.0写法

    事件生产者

    public class LongEventProducerWithTranslator { 
        //一个translator可以看做一个事件初始化器,publicEvent方法会调用它
        //填充Event
        private static final EventTranslatorOneArg<LongEvent, ByteBuffer> TRANSLATOR = 
                new EventTranslatorOneArg<LongEvent, ByteBuffer>() { 
                    public void translateTo(LongEvent event, long sequence, ByteBuffer bb) { 
                        event.setValue(bb.getLong(0)); 
                    } 
                };
        private final RingBuffer<LongEvent> ringBuffer;
        public LongEventProducerWithTranslator(RingBuffer<LongEvent> ringBuffer) { 
            this.ringBuffer = ringBuffer; 
        } 
     
        public void onData(ByteBuffer bb) { 
            ringBuffer.publishEvent(TRANSLATOR, bb); 
        } 
    } 
    

    Disruptor提供了不同的接口(EventTranslator, EventTranslatorOneArg, EventTranslatorTwoArg, 等等)去产生一个Translator对象。很明显,Translator中方法的参数是通过RingBuffer来传递的。

    使用Java 8

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

    public class LongEventMainJava8 { 
        /** 
         * 用lambda表达式来注册EventHandler和EventProductor 
         * @param args 
         * @throws InterruptedException 
         */public static void main(String[] args) throws InterruptedException { 
            // Executor that will be used to construct new threads for consumers 
            Executor executor = Executors.newCachedThreadPool();
            // 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, executor);
            // 可以使用lambda来注册一个EventHandler 
            disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event.getValue()));
            // 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); 
                ringBuffer.publishEvent((event, sequence, buffer) -> event.setValue(buffer.getLong(0)), bb); 
                Thread.sleep(1000); 
            } 
        } 
    } 
    

    单或多 事件生产者

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

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

    可选的等待策略

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

    参考

    Disruptor入门

    相关文章

      网友评论

          本文标题:Disruptor入门

          本文链接:https://www.haomeiwen.com/subject/syocwqtx.html