解读Disruptor系列--解读源码(0)之源码导读

本篇文章是后续解读Disruptor源码的导读,适合对Disruptor还不了解的同学。如果有兴趣,还可以看下我之前发的Disruptor系列文章。
要大概弄明白Disruptor是个什么玩意,可以先回答这几个问题:
Disruptor是什么?
为什么要用Disruptor?
Disruptor为什么那么快?

Disruptor是什么?

我也来个一句话总结:Disruptor是LMAX开源的、用于替代并发线程间数据交换的有界队列的、可选无锁的、高性能的线程间通讯框架。

听起来是不是感觉高大上?是不是觉得很好奇怎么个高性能?是不是还有点云里雾里咋交换数据?这也是我最初的感觉。
慢慢来,我们逐步分解这些名词。
Disruptor:首先我们看看Disruptor这个词。熟悉星际迷航(Star Trek)的同学可能对这个词会更熟悉,Disruptor(裂解炮)和Phaser(相位枪)都是星际迷航中的武器,Phaser是联邦武器,而Disruptor是克林贡的等价物。看起来Disruptor和Phaser是类似的,那Phaser又是啥呢?在jdk1.7中,Doug Lea大师为我们带来了Phaser,一个可重用的同步屏障,功能类似CyclicBarrier和CountDownLatch,但是使用更加灵活。
LMAX团队之所以为Disruptor起了这个名字,一方面是因为Disruptor和Phaser在处理依赖图表(或者说是消费链、多阶段处理、流水线)时有类似的地方;另一方面,Disruptor这个词本身就有分裂、破坏的意思,很符合Disruptor中的设计思想对于传统并发编程思想的某种“对立”--并发编程总是在想如何利用多线程去拥有更大的吞吐量、更少的延迟,而Disruptor却通过非常多的性能测试发现,在并发编程中正是过多的线程间通讯、争抢导致了性能瓶颈,而最终选择了单线程的处理逻辑去完成业务处理。
LMAX:LMAX是英国一个交易量很大的金融交易所,也是开源Disruptor的组织。
开源https://github.com/LMAX-Exchange/disruptor
并发线程间数据交换的有界队列 :在并发编程时,经常需要在线程间交换数据,如任务分发、事件传递、状态变更等, 这时通常需要多个线程去访问一个线程安全的数据结构,一般选择使用队列(Queue)实现。队列在容量上又分为有界和无界,使用无界队列通常都需要保证生产者生产速度小于消费者消费速度,否则将由于内存耗尽导致灾难结果。为了避免这种情况,队列通常是有界的。Java中经常使用BlockingQueue作为并发线程使用的有界队列,使用put()、take()在队列满、空的情况下进行等待,非常适合多线程间的数据共享,实现方式一般有ArrayBlockingQueue和LinkedBlockingQueue。
线程间通讯:等同于并发线程间的数据交换。
可选无锁:使用Disruptor唯一可能遇到Java锁的时候,就是在消费者等待可用事件进行消费时。而Disruptor为这个等待过程,编写了包括使用锁和不使用锁的多种策略,可根据不同场景和需求进行选择。
高性能:单生产者+单消费者性能测试:
使用LinkedBlockingQueue(https://github.com/LMAX-Exchange/disruptor/blob/master/src/perftest/java/com/lmax/disruptor/queue/OneToOneQueueThroughputTest.java

Starting Queue tests
Run 0, BlockingQueue=3,687,315 ops/sec
Run 1, BlockingQueue=2,721,088 ops/sec
Run 2, BlockingQueue=3,471,017 ops/sec
Run 3, BlockingQueue=5,347,593 ops/sec
Run 4, BlockingQueue=5,316,321 ops/sec
Run 5, BlockingQueue=5,449,591 ops/sec
Run 6, BlockingQueue=5,173,305 ops/sec

使用常用的带Translator的Disruptor测试(https://github.com/LMAX-Exchange/disruptor/blob/master/src/perftest/java/com/lmax/disruptor/translator/OneToOneTranslatorThroughputTest.java

Starting Disruptor tests
Run 0, Disruptor=20,222,446 ops/sec
Run 1, Disruptor=70,671,378 ops/sec
Run 2, Disruptor=37,878,787 ops/sec
Run 3, Disruptor=37,105,751 ops/sec
Run 4, Disruptor=38,986,354 ops/sec
Run 5, Disruptor=27,578,599 ops/sec
Run 6, Disruptor=34,281,796 ops/sec

综上的名词解释,聪明如你,想必已经明白个大概了。如果还是不明白,肯定是我讲的不好,请留言。

为什么要用Disruptor?

那为什么要用Disruptor呢?或者说Disruptor解决了什么问题呢?
一般情况下,新的发明创造都是伴随着对旧有事物的痛点产生的。在并发线程间交换数据这个问题上,使用传统的阻塞队列有什么问题呢?
根据LMAX团队在2011年发布的论文,可简单归结为以下几个问题:

  1. 锁的成本: 传统阻塞队列使用锁保证线程安全。而锁通过操作系统内核的上下文切换实现,会暂停线程去等待锁直到释放。执行这样的上下文切换,会丢失之前保存的数据和指令。由于消费者和生产者之间的速度差异,队列总是接近满或者空的状态。这种状态会导致高水平的写入争用。
  2. 伪共享问题导致的性能低下。
  3. 队列是垃圾的重要来源,队列中的元素和用于存储元素的节点对象需要进行频繁的重新分配。

论文中给出的,循环5亿次64位计数操作使用时间对比:

执行5亿次64位计数操作使用时间对比

Disruptor通过良好的设计,最大限度解决了上述三个问题。
好奇如你,难道就不想知道其中奥妙吗?

Disruptor为什么那么快?

简单来说,Disruptor在设计上有以下几点优势:

  1. 内部数据存储使用环形缓冲(Ring Buffer),在启动时进行对象内存分配,这个对象并非数据本身,而只是一个数据容器。这个容器由用户提供,在Disruptor运行时,生产者负责拿到容器设置好数据,消费者再去可用的容器中拿到数据完成消费。这样分配对象内存,将有极大可能让这些对象内存在主存中连续分配,从而支持了CPU缓存位置预测。否则每次new一个对象,很难知道对象内存分配到哪了。这样做好有个好处,环形缓冲在JVM生命周期中通常是永生的,GC的压力更小
  2. 尽量使用无锁设计,合理使用CAS。举两个例子。
    a. 通过区分是否允许并发生产者,细分单生产者模式和多生产者模式,单生产者单线程更新游标,多生产者模式使用CAS更新游标位置。
    b. 在阻塞型等待策略中,将等待分两部分。第一部分,消费者等待生产者发布新数据,由于不确定等待时间,使用锁的条件等待。而第二部分,消费者等待上一组消费者(Disruptor支持链式消费,类似Phaser分阶段处理) 完成此数据消费时,由于已确定有可消费数据,且假定通常的消费时间较短,所以使用自旋(忙循环)来避免上下文切换导致的性能开销。能不用锁就不用锁,即便要用锁,也要将使用锁的粒度用的最小。
  3. 优化数据结构,解决伪共享问题。Java中通过填充缓存行,来解决伪共享问题的思路,现在可能已经是老生常谈,连Java8中都新增了sun.misc.Contended注解来避免伪共享问题。但在Disruptor刚出道那会儿,能够以“机械同情(mechanical sympathy)”的思考方式,来优化Java数据结构,这恐怕还很新潮。
  4. 优化其他实现细节。举几个例子。
    a. 如一个RingBuffer,容量bufferSize为2的幂。使用sequence表示事件序号,可通过sequence & (bufferSize - 1)定位元素的index,比普通的求余取模(%)要快得多。事先计算好bufferSize以2为底的对数indexShift,可通过sequence >>> indexShift快速计算出sequence/bufferSize的商flag(其实相当于当前sequence在环形跑道上跑了几圈,在数据生产时要设置好flag,在数据消费时就可以通过比对flag判断此位置的数据是不是本圈的数据),比除法要快得多。
    b. 合理使用Unsafe,实现更加高效地内存管理和原子访问。Unsafe封装了一些类似C/C++中的指针操作,除了JDK,在Netty、Spring、Kafka、Storm等非常多的流行开源项目中都使用了Unsafe。

简单讲到这,后续开始分享解读Disruptor源码的内容。


参考资料
1.Java8使用@sun.misc.Contended避免伪共享 - http://www.jianshu.com/p/c3c108c3dcfd
2.写Java也得了解CPU--CPU缓存 http://www.cnblogs.com/techyc/p/3607085.html
3.linux查看CPU高速缓存(cache)信息 http://www.cnblogs.com/kekukele/p/3829369.html
4.理解 CPU Cache http://wsfdl.com/linux/2016/06/11/%E7%90%86%E8%A7%A3CPU%E7%9A%84cache.html
5.关于CPU Cache -- 程序猿需要知道的那些事 http://cenalulu.github.io/linux/all-about-cpu-cache/
6.Mechanical Sympathy - Memory Barriers/Fences https://mechanical-sympathy.blogspot.tw/2011/07/memory-barriersfences.html
7.《Java高并发程序设计》- 4.4.3 Java中的指针:Unsafe类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容