9、并发编程基础-高性能的生产者-消费者框架(Disruptor)

目录

1. 为什么要不选择BlockigQueue做生产者消费者缓存队列。
2. Disruptor的优势
3. 认识RingBuffer
4. 术语说明
5. 开发示例
6. 提高消费者的响应时间


1、为什么要不选择BlockigQueue做生产者消费者缓存队列。

BlockigQueue用于实现生产者和消费者是一个很不错的选择。它可以很方便的实现生产者和消费者之间的数据缓冲区。但是BlockigQueue我们我们知道他是使用锁和阻塞机制的,因此使用BlockigQueue实现生产者和消费并不是一个高性能的实现。但这并不是说他不好,只是说在高并发场合,它的性能并不是特别的优越,是一个带锁阻塞的生产者-消费者模式。

2、Disruptor的优势

Disruptor它是一个开源的并发框架,并获得2011 Duke’s 程序框架创新奖,能够在无锁的情况下实现网络的Queue并发操作。

Disruptor和BlockigQueue相比,Disruptor是一个无锁竞争的、异步的、高性能,或者是可以认为是最快的消息框架(轻量级的JMS),所以他非常合适实现生产者和消费者模式,比如消息的发布。

Disruptor底层存储数据的方式是使用环形队列(RingBuffer)实现的,它和传统的线性队列(BlockigQueue)相比,传统线性队列需要同时维护首部指针和尾部指针。而 RingBuffer它最大特点是没有尾部指针,只需要维护一个指向下一个可用元素的指针,实现进出队列。这样极大的优化了线程协作的复杂度(大家自己在写线程直接的协作工作的时候,都知道要对临界区进行加锁,等待,唤醒等操作,这在高并发是很消耗性能的)。

那么Disruptor的效率到底有多高。Martin Fowler在自己网站上写了一篇LMAX架构的文章,在文章中他介绍了LMAX是一种新型零售金融交易平台,它能够以很低的延迟产生大量交易。这个系统是建立在JVM平台上,其核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单。业务逻辑处理器完全是运行在内存中,使用事件源驱动方式。业务逻辑处理器的核心是Disruptor。

3、认识RingBuffer

1)RingBuffer,它是Disruptor的核心,那什么是RingBuffer。前面已经提供提是一个环形的数据结构,她的作用就是存储数据,实现不同线程之间的协同工作传递数据。

如下图: 环形RingBuffer的结构

这里写图片描述

2)RingBuffer每块区是拥有一个序号的,这个序号指向环形数组结构的下一个可用元素。

如下图:
这里写图片描述

3)随着不断地写进了填充这个圆环,这个指针序号会不断地递增,直到绕过这个环。
如下图:
这里写图片描述

当这个圆环满了之后,不会删除RingBuffer里面的数据,也就是说数据会一直存放在RingBuffer中。那么你可能就会有疑问了,如果继续有新数据到来怎么办法?
如果继续有新数据到来,那么它会把原来的旧数据覆盖,什么意思呢?

如上图:现在12的区域的下个区域目前是3,如果有新的数据到来,那么指针往下移的时候就会把区域3的数据给覆盖变成13。

当然,框架提供了一系列帮助我们平行消费的监控,会很好的控制生产者和消费者之间的速度,从而达到生产和消费之间的平衡。

4)RingBuffer之所以效率高,它还下面的原因。

  • RingBuffer是数组的,定位也是非常迅速,所以自然会比链表结构效率高。
  • 数组的内存分配是预先加载的,一但指定大小创建后,就一直存在。这也意味着不需要花大量的时间做垃圾回收。而链表结构需要为每个对象创建节点,然后删除节点,然后执行内存清理等。
4、术语说明
  • Sequence:采用缓存行填充的方式对long类型的一层包装,用以代表事件的序号。通过unsafe的cas方法从而避免了锁的开销;
  • Sequencer:生产者与缓存RingBuffer之间的桥梁。单生产者与多生产者分别对应于两个实现SingleProducerSequencer与MultiProducerSequencer。Sequencer用于向RingBuffer申请空间,使用publish方法通过waitStrategy通知所有在等待可消费事件的SequenceBarrier;
  • WaitStrategy:WaitStrategy有多种实现,用以表示当无可消费事件时,消费者的等待策略;
  • SequenceBarrier:消费者与缓存RingBuffer之间的桥梁。消费者并不直接访问RingBuffer,从而能减少RingBuffer上的并发冲突;
  • EventProcessor:事件处理器,是消费者线程池Executor的调度单元,是对事件处理EventHandler与异常处理ExceptionHandler等的一层封装;
  • Event: 消费事件。Event的具体实现由用户定义;
  • RingBuffer:基于数组的缓存实现,也是创建sequencer与定义WaitStrategy的入口;
  • Disruptor:Disruptor的使用入口。持有RingBuffer、消费者线程池Executor、消费者集合ConsumerRepository等引用。
5、生产者-消费(开发示例)

Mvaen开发,所需配置

<!-- disruptor高并发框架 -->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>

Main函数

import java.nio.ByteBuffer;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SequenceBarrier;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.WorkerPool;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.ProducerType;

public class Main {

    public static void main(String[] args) throws Exception {

        //创建RingBuffer
        RingBuffer<Order> ringBuffer = RingBuffer.create(ProducerType.MULTI, //生产类型,MULTI表示可以多个生产者
                        new EventFactory<Order>() {  
                            @Override  
                            public Order newInstance() {  
                                return new Order();  
                            }  
                        }, 
                        1024 * 1024, 
                        new YieldingWaitStrategy());

        //构建消费者与缓存RingBuffer之间的桥梁。消费者并不直接访问RingBuffer,从而能减少RingBuffer上的并发冲突。
        SequenceBarrier barriers = ringBuffer.newBarrier();

        //创建多个消费者
        Consumer[] consumers = new Consumer[3];
        for(int i = 0; i < consumers.length; i++){
            consumers[i] = new Consumer("c" + i);
        }

        WorkerPool<Order> workerPool =
                new WorkerPool<Order>(ringBuffer,
                        barriers,
                        new IntEventExceptionHandler(),
                        consumers);

        //将消费进度仍给生产者监控,用来平衡生产消费
        ringBuffer.addGatingSequences(workerPool.getWorkerSequences());
        ExecutorService executor =Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        //执行消费
        workerPool.start(executor);
        final CountDownLatch latch = new CountDownLatch(1);

        //创建生产者
        for (int i = 0; i < 100; i++) {  
            final Producer p = new Producer(ringBuffer);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //使当前线程等待,直到锁向下计数为零,除非线程 interrupted。
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for(int j = 0; j < 100; j ++){
                        p.onData(UUID.randomUUID().toString());
                    }
                }
            }).start();
        }

        Thread.sleep(2000);
        System.out.println("---------------开始生产-----------------");
        latch.countDown();
        Thread.sleep(5000);
        System.out.println("总数:" + consumers[0].getCount() );
    }

    //异常处理器
    static class IntEventExceptionHandler implements ExceptionHandler {  
        @Override
        public void handleEventException(Throwable ex, long sequence, Object event) {}
        @Override
        public void handleOnStartException(Throwable ex) {}
        @Override
        public void handleOnShutdownException(Throwable ex) {}
    } 
}

生产者

import java.nio.ByteBuffer;
import java.util.UUID;

import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.RingBuffer;

//生产者
public class Producer {

    private final RingBuffer<Order> ringBuffer;

    public Producer(RingBuffer<Order> ringBuffer){
        this.ringBuffer = ringBuffer;
    }

    /**
     * onData用来发布事件,每调用一次就发布一次事件
     * 它的参数会用过事件传递给消费者
     */
    public void onData(String data){
        //可以把ringBuffer看做一个事件队列,那么next就是得到下面一个事件槽
        long sequence = ringBuffer.next();
        try {
            //用上面的索引取出一个空的事件用于填充(获取该序号对应的事件对象)
            Order order = ringBuffer.get(sequence);
            //获取要通过事件传递的业务数据
            order.setId(data);
        } finally {
            //发布事件
            //注意,最后的 ringBuffer.publish 方法必须包含在 finally 中以确保必须得到调用;如果某个请求的 sequence 未被提交,将会堵塞后续的发布操作或者其它的 producer。
            ringBuffer.publish(sequence);
        }
    }

}

消费者

import java.util.concurrent.atomic.AtomicInteger;

import com.lmax.disruptor.WorkHandler;

//消费者,WorkHandler接口来自框架
public class Consumer implements WorkHandler<Order>{

    private String consumerId;

    //计算器
    private static AtomicInteger count = new AtomicInteger(0);

    public Consumer(String consumerId){
        this.consumerId = consumerId;
    }

    @Override
    public void onEvent(Order order) throws Exception {

        //这里是消费逻辑,onEvent()方法回进行回到,数据读取全部已经由框架负责封装。
        System.out.println("当前消费者: " + this.consumerId + ",消费信息:" + order.getId());
        count.incrementAndGet();
    }

    public int getCount(){
        return count.get();
    }

}

将被生产的产品

import lombok.Data;

//订单
@Data
public class Order {
    private String id;//ID
    private String name;//名称
    private double price;//金额
}
6、WaitStrategy可选的等待策略,提高消息者的等待时间

当数据在环形缓存区(RingBuffer)的产生时,消息者如何知道这些新产生的数据呢?就是通过WaitStrategy策略来监控的,为此,Disruptor提供了几种策略,这些策略由WaitStrategy接口进行封装,主要有以下几种:

BlockingWaitStrategy
这是默认的策略。使用 BlockingWaitStrategy 和使用BlockingQueue非常类似,它们都使用锁和条件(Conditon)进行数据的监控和线程的唤醒。因为涉及到线程切换,BlockingWaitStrategy策略是最节省CPU,但是高并发下性能表现最糟糕的一种等待策略。

SleepingWaitStrategy
这策略也是对cpu非常保守的。它会循环中不断等待数据。和BlockingWaitStrategy一样,SpleepingWaitStrategy的CPU使用率也比较低。它的方式是循环等待并且在循环中间调用LockSupport.parkNanos(1)来睡眠,(在Linux系统上面睡眠时间60µs).然而,它的优点在于生产线程只需要计数,而不执行任何指令。并且没有条件变量的消耗。但是,事件对象从生产者到消费者传递的延迟变大了。SleepingWaitStrategy最好用在不需要低延迟,而且事件发布对于生产者的影响比较小的情况下。比如异步日志功能。

YieldingWaitStrategy
这个策略用于低延迟时的场合。YieldingWaitStrategy是可以被用在低延迟系统中的两个策略之一,这种策略在减低系统延迟的同时也会增加CPU运算量。YieldingWaitStrategy策略会循环等待sequence增加到合适的值。循环中调用Thread.yield()允许其他准备好的线程执行。如你需要一个高性能系统,并且延时有较为严格的要求,则可以考虑这种测试。例如:在开启超线程的时候。

BusySpinWaitStrategy
BusySpinWaitStrategy是性能最高,最疯狂的等待策略,同时也是对部署环境要求最高的策略,底层是一个死循环,消费线程会尽最大的努力去监控RingBuffer的变化。这个性能最好用在事件处理线程比物理内核数目还要小的时候。例如:在禁用超线程技术的时候。


参考书籍:java高并发程序设计
参考资料:并发编程网

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值