Java高性能异步并发框架Disruptor

Java的内置队列

队列有界性数据结构
ArrayBlockingQueuebounded加锁arraylist
LinkedBlockingQueueoptionally-bounded加锁linkedlist
ConcurrentLinkedQueueunbounded无锁(CAS)linkedlist
LinkedTransferQueueunbounded无锁(CAS)linkedlist
PriorityBlockingQueueunbounded加锁heap
DelayQueueunbounded加锁heap

队列的底层一般分成三种:数组、链表和堆。其中,堆一般情况下是为了实现带有优先级特性的队列

Disruptor核心

  • RingBuffer Disruptor
  • Sequence SequenceBarrier
  • WaitStrategy等待策略
  • EventHandler消费者处理器
  • WorkProcessor核心工作器

Disruptor

Martin Fowler在自己网站上写了一篇LMAX架构的文章,在文章种介绍了LMAX是一种 新型零售金融交易平台,它能够以很低的延迟产生大量交易,这个系统是建立在jvm平台上, 其核心是一个业务逻辑处理器:

  • 它能够在一个线程里面每秒处理600万订单
  • 业务逻辑处理器完全是运行在内存中,使用事件驱动方式
  • 业务逻辑处理器的核心是Disruptor

Disruptor为什么高性能

  • 数据结构:使用环形队列、数组、内存预加载
  • 消除伪共享(填充缓存行)
  • 使用单线程写方式,内存屏障(volatile变量)
  • 序号栅栏和序号配合使用来消除锁和CAS

系统缓存优化-消除伪共享

  • 缓存系统中是以缓存行(cache line)为单位存储的
  • 缓存行是2的整数幂个连续字节,一般为32-256个字节
  • 最常见的缓存行大小是64个字节
  • Sequence

Disruptor 核心原理

  • 一个环状队列,用在不同线程之间传递数据
  • RingBuffer拥有一个序号,这个序号指向数组中下一个可用元素
  • RingBuffer:基于数组的缓存实现,也是创建sequencer与定义WaitStrategy的入口
  • Disruptor:持有RingBuffer、消费者线程池Executor、消费者集合ConsumerRepository等引用

Disruptor 核心-Sequence

  • 通过顺序递增的序号来编号,管理进行交换的数据
  • 对数据的处理过程总是沿着序号逐渐处理
  • 一个Sequence用于跟踪标识某个特定的事件处理者(RingBuffer/Producer/Consumer)的处理进度, 多个Producer共用一个Sequence,但每一个Consumer分别对应一个Sequence, 当Producer获取的Sequence大于多个Consumer中的最小Sequence时,则等待,不再继续投递数据.
  • Sequence可以看成是一个AtomicLong用于标识进度
  • Sequence可以消除CPU缓存伪共享的问题

Disruptor 核心-Sequencer

  • 实现类SingleProducerSequencer和MultiProducerSequencer
  • 生产者和消费者之间快速,正确的传递数据的并发算法

Disruptor 核心-Sequencer Barrier

  • 用于保持对RingBuffer的Main Published Sequence(Producer)和Consumer之间的平衡关系;
  • Sequence Barrier用于判断决定Consumer是否还有可处理的事件.

Disruptor 核心-Event

  • 从生产者到消费者过程中处理的数据
  • Disruptor中没有代码表示Event,而是用户自定义的,其实就是对应的处理数据实体类

Disruptor 核心-EventProcessor

  • 继承Runnable接口,处理Disruptor中的Event,拥有消费者的Sequence
  • 其实现类BatchEventProcessor,包含了Event loop有效的实现,并且将回调到一个EventHandler接口实现

Disruptor 核心-EventHandler

  • 代表一个消费者,用于处理队列中的数据,单消费者模式中使用
  • 线程池数量必须等于EventHandler数量.

Disruptor 核心-WorkHandler

Disruptor 核心-WorkProcessor

确保每个Sequence只被一个processor消费,在同一个workPool中处理多个WorkProcessor不会消费同样的Sequence

Disruptor 核心-WaitStrategy

  • 决定向队列添加数据时的等待策略

BlockingWaitStrategy

最低效的策略,但其对CPU的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现, 内部使用了ReentrantLock

SleepingWaitStrategy

性能表现跟BlockingWaitStrategy差不多,对CPU的消耗也类似,但其对生产者线程的影响最小, 适用于异步日志类的场景

YieldingWaitStrategy

性能是最好的,适用于低延时的系统. 在要求极高性能且事件处理线程数小于CPU逻辑核心数的场景中, 推荐使用此策略;例如,CPU开启超线程的特性.
该策略慎用: 内部采用Thread.yield();会让消费线程让出cpu,但又会在让出后同时竞争资源,导致CPU飙升.

disruptor.shutdown();

  • 会等待所有的消费者消费完成后关闭,
  • 但是不能在数据生产结束前调用shutdown,否则会导致过早的关闭disruptor,但是此时会继续投递数据, 而获取next Sequence时会导致死循环,因为(获取的生产者Sequence大于消费者Sequence,认为有数据没有消费完, 不能向该Sequence位置添加新数据,但是消费者此时已经被关闭,消费者Sequence不会再改变, 所以出现无限循环等待,无法继续向队列添加数据)

WorkerPool实现多消费者模式

单消费者模式中,虽然一个Handler对应一个线程,但是这些Handler处理的是同一个数据,所以是单消费者模式.
多消费者模式: 多个线程处理的是不同的数据,队列中的数据只会被一条线程处理.

Disruptor中消费线程 串行操作(单消费者模式)

  // 一个Handler对应一条线程,串行操作
 
  disruptor.handleEventsWith(new Handler1())
           .handleEventsWith(new Handler2())
           .handleEventsWith(new Handler3());

Disruptor中消费线程 并行操作(单消费者模式)

  // 一个Handler对应一条线程,并行操作
  
  disruptor.handleEventsWith(new Handler1());
  disruptor.handleEventsWith(new Handler2());
  disruptor.handleEventsWith(new Handler3());
  // 一个Handler对应一条线程,并行操作
  
  disruptor.handleEventsWith(new Handler1(), new Handler2(), new Handler3());

Disruptor中消费线程 串并行混合操作(单消费者模式)

  //Handler1 Handler2 并行执行完成后,串执行Handler3
  disruptor.handleEventsWith(new Handler1(), new Handler2())
           .handleEventsWith(new Handler3());
  //Handler1 Handler2 并行执行完成后,串执行Handler3
  EventHandlerGroup<Trade> group = disruptor.handleEventsWith(new Handler1(), new Handler2());
  group.then(new Handler3());

6边形操作(单消费者模式)

                 --> h1 --> h2 -->
              --                   --
    start -->                        --> h3
              --                   --
                 --> h4 --> h5 -->
        
    h1和h4并行,h1和h2串行,h4和h5串行,h2和h5全部完成后执行 h3

    Handler1 h1 = new Handler1();
    Handler2 h2 = new Handler2();
    Handler3 h3 = new Handler3();
    Handler4 h4 = new Handler4();
    Handler5 h5 = new Handler5();

    disruptor.handleEventsWith(h1,h4);
    disruptor.after(h1).handleEventsWith(h2);
    disruptor.after(h4).handleEventsWith(h5);
    disruptor.after(h2,h5).handleEventsWith(h3);

线程池设置参考规则

  • 计算机密集型,耗cpu, 一般是cpu核心数+1或cpu核心数*2
  • IO密集型,一般是 cpu核心数 / (1-0.9) 或者 cpu核心数 / (1-0.8) 例如:8核处理器, 8 / (1-0.9) = 80 条io线程

AOS架构

AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架.

  • AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)
  • AQS定义两种资源共享方式: Exclusive, Share
isHeldExclusively 方法: 该线程是否正在独占资源
tryAcquire/tryRelease方法: 独占的方式尝试获取和释放资源
tryAcquireShared/tryReleaseShared方法: 共享方式

参考: https://www.jianshu.com/p/da9d051dcc3d

ReentrantLock 重入锁

  1. state初始值为0,表示未锁定状态
  2. A线程lock时,会调用tryAcquire()独占该锁并将 state + 1
  3. 其它线程再tryAcquire()时就会失败,直到A线程unlock()到state=0为止,其他线程才会有机会获取该锁
  4. A线程释放之前,A线程自己可以重复获取,此锁的(state会累加),这就是可重入的概念
  5. 但是要注意,获取多少次就要释放多少次,这样才能保证state是能回到0.

独享锁:该锁每一次只能被一个线程所持有。

共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。

com.lmax.disruptor.SingleProducerSequencer.next(int) 源码分析

@Override
public long next(int n)
{
    if (n < 1)
    {
        throw new IllegalArgumentException("n must be > 0");
    }

    long nextValue = this.nextValue;

    long nextSequence = nextValue + n;
    
    //wrapPoint用于判断当前的序号有没有绕过整个RingBuffer容器
    //相当于标记生产者在队列中的逻辑队尾位置,
    long wrapPoint = nextSequence - bufferSize;
    
    //记录最小消费者序号
    long cachedGatingSequence = this.cachedValue;

    //如果生产者序号大于最小消费者序号,则可能需要等待
    if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
    {
        cursor.setVolatile(nextValue);  // StoreLoad fence

        //最小的序号
        long minSequence;
        
        //如果生产者序号大于消费者中最小的序号,则自旋,等待空间
        while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue)))
        {
            LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin?
        }

        this.cachedValue = minSequence;
    }

    this.nextValue = nextSequence;

    return nextSequence;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值