核心设计原理Disruptor通过以下设计来解决队列速度慢的问题:
「环形数组结构:」
为了避免垃圾回收 , 采用数组而非链表 。同时 , 数组对处理器的缓存机制更加友好
?「元素位置定位:」
原因:CPU缓存是由很多个缓存行组成的 。每个缓存行通常是64字节 , 并且它有效地引用主内存中的一块儿地址 。一个Java的long类型变量是8字节 , 因此在一个缓存行中可以存8个long类型的变量 。CPU每次从主存中拉取数据时 , 会把相邻的数据也存入同一个缓存行 。在访问一个long数组的时候 , 如果数组中的一个值被加载到缓存中 , 它会自动加载另外7个 。因此你能非常快的遍历这个数组 。
?
数组长度2^n , 通过位运算 , 加快定位的速度 。下标采取递增的形式 。不用担心index溢出的问题 。index是long类型 , 即使100万QPS的处理速度 , 也需要30万年才能用完 。
「无锁设计:」
每个生产者或者消费者线程 , 会先申请可以操作的元素在数组中的位置 , 申请到之后 , 直接在该位置写入或者读取数据 , 整个过程通过原子变量CAS , 保证操作的线程安全
数据结构框架使用RingBuffer来作为队列的数据结构 , RingBuffer就是一个可自定义大小的环形数组 。
除数组外还有一个序列号(sequence) , 用以指向下一个可用的元素 , 供生产者与消费者使用 。
原理图如下所示:
文章插图
Sequencemark:Disruptor通过顺序递增的序号来编号管理通过其进行交换的数据(事件) , 对数据(事件)的处理过程总是沿着序号逐个递增处理 。
【高性能无锁并发框架Disruptor,太强了】「数组+序列号设计的优势是什么呢?」
回顾一下HashMap , 在知道索引(index)下标的情况下 , 存与取数组上的元素时间复杂度只有O(1) , 而这个index我们可以通过序列号与数组的长度取模来计算得出 , index=sequence % table.length 。当然也可以用位运算来计算效率更高 , 此时table.length必须是2的幂次方 。
写数据流程单线程写数据的流程:
- 申请写入m个元素;
- 若是有m个元素可以入 , 则返回最大的序列号 。这儿主要判断是否会覆盖未读的元素;
- 若是返回的正确 , 则生产者开始写入元素 。
文章插图
使用场景经过测试 , Disruptor的的延时和吞吐量都比ArrayBlockingQueue优秀很多 , 所以 , 当你在使用ArrayBlockingQueue出现性能瓶颈的时候 , 你就可以考虑采用Disruptor的代替 。
参考:github.com/LMAX-Exchan…
文章插图
文章插图
当然 , Disruptor性能高并不是必然的 , 所以 , 是否使用还得经过测试 。
Disruptor的最常用的场景就是“生产者-消费者”场景 , 对场景的就是“一个生产者、多个消费者”的场景 , 并且要求顺序处理 。
举个例子 , 我们从MySQL的BigLog文件中顺序读取数据 , 然后写入到ElasticSearch(搜索引擎)中 。在这种场景下 , BigLog要求一个文件一个生产者 , 那个是一个生产者 。而写入到ElasticSearch , 则严格要求顺序 , 否则会出现问题 , 所以通常意义上的多消费者线程无法解决该问题 , 如果通过加锁 , 则性能大打折扣
作者:月伴飞鱼
链接:https://juejin.im/post/6869795029800452103
来源:掘金
推荐阅读
- Tinyid 深度解密滴滴的高性能ID生成器
- Java 并发基础之内存模型
- 网络高并发
- 10大高性能开发宝石,我要消灭一半程序员
- Linux定时器 - 高性能定时器
- 抢鲜!阿里架构师私藏并发编程笔记,公开前半段秒获8K标星
- JAVA并发-AtomicInteger
- 如何进行Jmeter多接口指定TPS同时并发?
- 喝什么茶能提高性能力,喝什么茶能软化血管呢
- M.2接口SSD就是高性能存储的代名词?不同插槽代表不同速度