栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

无锁并发框架Disruptor

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

无锁并发框架Disruptor

Disruptor:

介绍:是一个高性能的异步处理框架,研发的初衷是为了解决高并发情况下队列加锁的问题,使用效果和BlockingQueue很类似,号称“一个线程一秒可以处理600万订单”(惊呆了);

核心设计组件:
RingBuffer:环形缓冲区,其内部定义了数组、sequencer等属性,利用数组存储结构达到了环形数组的效果;但存储的数组存储达或获取到最后一位时,又开始重第一位开始存储获取获取;
无锁设计:每个生产者或消费者会先申请可以操作的元素在数组中的位置,申请到之后,直接在该下标位置进行读取或写入;
数组的长度只能是2的指数次幂,利用位运算,加快下标的定位;

sequence序号:利用生产者和消费者维护的序号来定位ringBuffer的位置,并且这个sequence是一直递增的,添加一个元素或消费一个元素,对应消费者和生产者各自维护的sequence就会加1;比如说一个RingBuffer的大小是8,序号从0开始增长,当sequence=7时,相当于转了一圈,如果继续增长的话,下一个获取或者写入的下标会从0开始;也即是说sequence%ringBuffer.length取模计算出操作ringBuffer的下标;
由于涉及到取模操作,没有位运算高效,所以为了使用位运算,定义RingBuffer的大小是2的N次方;hashMap中的h & (length-1); h=sequence
这里发现了和其它队列不同的的是没有元素的删除操作,只有覆盖操作,数组里的元素对象都是复用的;

无锁机制:
在生产者/消费者模式下,distuptor号称“无锁框架”(BlockingQueue是利用Lock锁做到的),这是怎么做到的那?

一个生产者+一个消费者:
生产者自己维护了一个sequence序号,消费者自己维护了sequence序号,两者各操作各的,不需要加锁;但是需要注意的是两者操作的下标不能大于环形数组一圈,也就是当一个下标中的元素没有被取出时,生产者不能对这个下标位置的元素进行覆盖的,需要进行等待该下标元素被消费者取出之后才能被覆盖,这里的等待策略有很多种,后面介绍;
同样消费者如果消费速度过快,则也需要等待,也是使用四种等待策略,当要读取的下标有元素时,该消费者就会唤醒等待的消费者;

N个消费者+N个生产者
每个消费者都各自维护者自己的sequence序号,多个生产者只维护了一个sequence序号,否则数据就乱套了,这里是通过CAS来保证生产者sequence序号的线程安全,也是没有用到锁;这里生产者的sequence序号也是不能超过消费者最小sequence序号一圈的,就是任意消费者的下标元素还没消费,就不能被生产者覆盖;

等待策略:都是实现了WaitStrategy接口
Disruptor的默认策略是BlockingWaitStrategy。在BlockingWaitStrategy内部是使 用锁和condition来控制线程的唤醒。BlockingWaitStrategy是最低效的策略,但其对CPU 的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现。SleepingWaitStrategy
SleepingWaitStrategy 的性能表现跟 BlockingWaitStrategy 差不多,对 CPU 的消耗也类似,但其对生产者线程的影响最小,通过使用LockSupport.parkNanos(1)来实现循环等待。一般来说Linux系统会暂停一个线程约60µs,这样做的好处是,生产线程不需要采取任何其他行动就可以增加适当的计数器,也不需要花费时间信号通知条件变量。但是,在生产者线程和使用者线程之间移动事件的平均延迟会更高。它在不需要低延迟并且对生产线程的影响较小的情况最好。一个常见的用例是异步日志记录。
YieldingWaitStrategy:YieldingWaitStrategy是可以使用在低延迟系统的策略之一。YieldingWaitStrategy 将自旋以等待序列增加到适当的值。在循环体内,将调用Thread.yield(),以允许其他排队的线程运行。在要求极高性能且事件处理线数小于 CPU 逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。
BusySpinWaitStrategy:性能最好,适合用于低延迟的系统。在要求极高性能且事件处理线程数小于CPU逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。

下面直接演示disrutor的用法:

@Data
public class OrderEvent {
    private int id;
}



public class OrderEventFactory implements EventFactory {
    @Override
    public OrderEvent newInstance() {
        return new OrderEvent();
    }
}



public class OrderEventHandler implements EventHandler {
    @Override
    public void onEvent(OrderEvent orderEvent, long l, boolean b) throws Exception {
        System.out.println(Thread.currentThread().getName()+"消费者:"+orderEvent.getId());
    }
}




public class OrderEventProducer {
    private final RingBuffer ringBuffer;

    public OrderEventProducer(RingBuffer ringBuffer) {
        this.ringBuffer = ringBuffer;
    }

    public void onData(int id) {
        // ringBuffer获取下一个 槽位
        long sequence = ringBuffer.next();

        //获取空的事件,这样就可以复用事件,减少GC;每个槽位对应固定的一个事件
        OrderEvent orderEvent = ringBuffer.get(sequence);
        //业务数据的封装
        orderEvent.setId(id);

        //发布事件
        ringBuffer.publish(sequence);
        System.out.println(Thread.currentThread().getName()+"事件已发布:"+id);
    }
}


//Main运行
public class DisruptorDemo {
    public static void main(String[] args) throws InterruptedException {
        //提供一个线程池,里面的线程作为消费者(Consumer)的事件处理线程
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //创建事件工厂(业务数据载体工厂)
        OrderEventFactory orderEventFactory = new OrderEventFactory();
        //创建环形数组的大小,一定是2的指数次幂
        int ringBuffferSize = 1024;
        //创建Disrutor
        Disruptor disruptor = new Disruptor(orderEventFactory, ringBuffferSize,
                threadPool, ProducerType.MULTI, new YieldingWaitStrategy());

        //设置事件处理器,也就是消费者,这里设置了两个消费者,一个线程要对应一个消费者,同一个事件被两个消费者消费
        EventHandlerGroup handlerGroup = disruptor.handleEventsWith(new OrderEventHandler(), new OrderEventHandler());
       //这个设置前面两个消费者消费完之后,再消费
        //这里可以设置 消费顺序,还有after()定义更灵活的先后顺序
        handlerGroup.then(new OrderEventHandler());

        //启动disruptor
        disruptor.start();

        //获取ringBuffer 环形缓冲区,其内部存储的结构是数组
        RingBuffer ringBuffer = disruptor.getRingBuffer();
        //创建自定义的生产者,其实就是传入ringBuffer,然后往里面添加数据
        OrderEventProducer eventProducer = new OrderEventProducer(ringBuffer);

        //添加数据到生产者中进行发布
        for (int i = 1; i < 3; i++) {
            eventProducer.onData(i);
        }
        Thread.sleep(1000);
        threadPool.shutdown();
        disruptor.shutdown();

    }
}

结果为:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1003350.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号