高性能队列——Disruptor

良心公众号

关注不迷路

Disruptor 是英国外汇交易公司 LMAX 开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与 I / O 操作处于同样的数量级)。基于 Disruptor 开发的系统单线程能支撑每秒 600 万订单,2010 年在 QCon 演讲后,获得了业界关注。2011 年,企业应用软件专家 Martin Fowler 专门撰写长文介绍。同年它还获得了 Oracle 官方的 Duke 大奖。

01

ArrayBlockingQueue 的优劣

在日常开发工作中,我们经常会用到 JDK 自带的一些队列的实现,其中最常用的莫过于 ArrayBlockingQueue 这种队列实现。

ArrayBlockingQueue 之所以受到广大开发者的青睐,主要有以下两个原因:

  • 出于系统稳定性的考虑,使用无界队列会有内存溢出的风险,而使用有界队列则不会有这样的风险,而 ArrayBlockingQueue 就是一个有界队列;

  • 另一方面,出于对性能的考虑,为了降低垃圾回收对系统性能的影响,基于数组的实现将会比基于链表的实现更加有优势,而且数组申请的是连续的存储空间,因此对处理器的缓存机制更加友好。

基于上述的优势,ArrayBlockingQueue 成为了 JDK 提供的队列实现中比较受欢迎的一种选择。但为什么 ArrayBlockingQueue 没有被称为高性能队列呢?说明它还不够高效,影响其性能的原因主要有以下两方面:

  • ArrayBlockingQueue 是通过加锁的方式来保证线程安全,频繁的加锁解锁会严重影响性能;

  • 存在伪共享, ArrayBlockingQueue 不能充分利用计算机缓存层次结构的缓存行,严重影响性能。

对于第一点,加锁会使线程挂起,从而导致性能下降,我们已经比较熟悉了。接下来我们就重点来谈谈什么是伪共享,以及为什么伪共享会严重影响性能。

要一下子讲清楚伪共享并是不那么容易的,我们需要一些知识做铺垫。因此,在谈伪共享之前,必须得先谈谈共享。而要谈共享,就绕不开计算机的存储器层次结构。接下来,我们逐一来看。

02

计算机的存储器层次结构

计算机系统是由硬件系统软件组成的,它们共同工作来运行应用程序。三者的关系如下图所示:

其中,计算机系统的硬件部分,处理器、主存和 I/O 设备三者之间的速度差异是异常巨大的,具体表现为处理器 >> 主存 >> I/O 设备。而为了提升系统的性能,便引入了缓存的概念。


其实,大可以这么理解,对处理器而言,主存是 I/O 设备的缓存,而如果要提升性能,可以在处理器和主存之间再增加若干层高速缓存。高速缓存的思想催生了存储器层次结构的形成。一个存储器层次结构由低速大容量到高速小容量依次为:远程二级存储(分布式文件系统,Web 服务器:L6) → 本地二级存储(本地磁盘:L5) → 主存(DRAM:L4) → 高速缓存(SRAM:L3 → L2 → L1) → 寄存器(L0)

03

共享

了解计算机的存储器分层结构之后,我们可以知道,当 CPU 执行运算的时候,会先到 L1 级缓存中去查找所需要的数据,如果找不到,就会到 L2 级缓存中去查找,然后依此类推,如果高速缓存中没有,就要访问主存。访问的路径越长,效率就越低。每经过一个层级,效率是成数量级的趋势下降的。

其中,高速缓存和主存与 CPU 核的对应关系如下图所示:

在上图中,L1 级缓存紧邻 CPU1 核,其大小很小但速度很快;L2 级缓存稍大些,稍慢些,它同 L1 级缓存一样,都是被一个单独的 CPU 核使用;而 L3 级缓存则更大些,更慢些,而且它是被单个插槽上的所有 CPU 核所共享的;而对于主存而言,其大小更是远远大于 L3 级缓存,速度上也是数量级的差别,而且主存是由全部 CPU 核所共享。

04

程序的局部性原理

程序的局部性原理是指程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。

程序的局部性原理又可具体表现为两个方面:时间局部性和空间局部性。

  • 时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。

  • 空间局部性是指一旦程序访问了某个存储单元,则不久之后,其附近的存储单元也将被访问。

05

缓存行

各级高速缓存是由很多个缓存行组成的。单个缓存行的大小通常为 64 个字节。根据程序的局部性原理,CPU 从主存中获取数据的时候,会将相邻的数据也存入同一个缓存行,从而提升效率。

06

伪共享

在有了上面一系列的知识铺垫之后,我们终于可以讲清楚伪共享了。在上文中,我们提到,根据程序的局部性原理,CPU 从主存中获取数据的时候,会将相邻的数据也存入同一个缓存行。

以 JDK11 的 ArrayBlockingQueue 为例,其部分成员变量如下所示:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    /** The queued items */
    final Object[] items;


    /** items index for next take, poll, peek or remove */
    int takeIndex;


    /** items index for next put, offer, or add */
    int putIndex;


    /** Number of elements in the queue */
    int count;


    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */


    /** Main lock guarding all access */
    final ReentrantLock lock;


    /** Condition for waiting takes */
    private final Condition notEmpty;


    /** Condition for waiting puts */
    private final Condition notFull;


    /**
     * Shared state for currently active iterators, or null if there
     * are known not to be any.  Allows queue operations to update
     * iterator state.
     */
    transient Itrs itrs;
}

当 CPU 从内存中加载出队索引 takeIndex 时,会同时加载入队索引 putIndex 和队列数组 item 中的元素总数 count

假设此时有两个线程,命名为线程 1 和线程 2,分别运行于 CPU1 和 CPU2 两个核上,线程 1 执行队列的入队操作,此时入队索引 putIndex 会被修改,而此次修改会导致所有 CPU 核上的相应缓存行全部失效,因此,线程 2 想要执行出队的操作时,必须再从内存中重新读取。而这种现象,就是伪共享。

伪共享会导致缓存行无法发挥作用,存储器的层次结构在这种场景下就无法起到提升效率的作用,甚至会起到反作用。

07

Disruptor 高性能的秘密

了解了什么是伪共享之后,我们再看看 Disruptor 被称为高性能队列的原因。它在拥有ArrayBlockingQueue 优势的基础上,也改进了 ArrayBlockingQueue 的缺点。

首先,Disruptor 是基于 RingBuffer 的环形数组结构,数组元素在初始化时一次性全部创建,数组元素的存储地址大概率是连续的,根据程序的局部性原理,能够提升缓存命中率。而且数组的长度为 2 ^ n,可以通过位运算加速元素位置定位的速度。

其次,Disruptor  采用空间换时间的方式,通过缓存行填充的方式,保证每个变量独占一个缓存行,从而取消了缓存行的共享,也就从根本上解决了伪共享的问题。

另外,Disruptor  采用 CAS 无锁算法保证操作的线程安全,避免了频繁加锁、解锁带来的性能损耗。

至此,我们已经了解了 Disruptor  被称为高性能队列的始末原委。后续文章中,将会进一步对 Disruptor  的源码进行分析,敬请期待~

欢迎关注【有理想的菜鸡】公众号,大家一起讨论技术,共同成长!

学习 | 工作 | 分享

????长按关注“有理想的菜鸡

只有你想不到,没有你学不到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值