原文地址:Concurrency with LMAX Disruptor – An Introductionhtml
前些天在并发编程网,看到了关于 Disruptor 的介绍。感受此框架惊为天人,值得学习学习。在把并发编程网上面介绍逐一浏览以后发觉,缺乏了对于 Disruptor 基础应用的介绍。因而就有了翻译海外基础介绍的想法。git
要为之后难以在工做中用到 Disruptor 而感到沮丧。由于据介绍来看,它号称"可以在一个线程里每秒处理6百万订单" 。我所在的平台撑不起这个量,同时也限于学历跟从业背景难以去这类大公司供职。github
追逐性能,经常来讲你给老板省了多少硬件,老板是看不到的。
建议一开始仍是不要设计得性能太过优秀,否则老板看不到你的价值。编程
Disruptor 是一个在并发编程中避免资源竞争的容器,用于协调生产者与消费者之间的关系,同时有着领域驱动模型 CQRS框架那种基于命令的影子。
应用这个框架编写代码将会较为繁复,模块与模块以前的通讯全由一个又一个Event类来协调。
相对于大多数喜欢一个方法到底的开发同窗来讲会比较麻烦,毕竟须要定义更多类。数组
本篇文章目的在于介绍 LMAX Disruptor,探讨它是如何帮助咱们实现软件低延迟、高并发特性。
咱们还将介绍 Disruptor 库的基本用法。缓存
Disruptor 是由 LMAX 编写的开源Java
库。它是个并发编程框架,用于处理大量事务,并且低延迟(然而并不会像常规并发代码那样复杂)。
如此高效的性能优化,是经过更高效的利用底层硬件的设计实现。性能优化
让咱们从机械情怀的核心概念开始 - 这就是了解底层硬件如何以最屌的方式运行。数据结构
举个栗子,架构
到CPU的延迟 | CPU时钟 | 耗时 |
---|---|---|
主内存 | 不少(Multiple) | ~60-80 ns |
L3 缓存 | ~40-45 周期 | ~15 ns |
L2 缓存 | ~10 周期 | ~3 ns |
L1 缓存 | ~3-4 周期 | ~1 ns |
寄存器 | 1 周期 | ~15 ns |
生产者和消费者之间经常速率不一致,队列一般老是为"空"或"满"。所以队列头(head)、队列尾(tail)和队列大小(size)有着资源竞争(write contention)。生产和消费不多达到和谐的状态。并发
一般采用锁来解决资源竞争(write contention)问题,但与此同时又会陷入内核级别的上下文切换。当这种状况发生时,处理器所缓存的数据可能丢失。(译者注:当线程A、B分别在CPU上不一样的两个内核上运行时,线程A正要更新变量Y。不幸的是,这个变量也同时正要被线程B所更新。若是核心A得到了全部权,缓存子系统将会使核心B中对应的缓存行失效。当核心B得到了全部权而后执行更新操做,核心A就要使本身对应的缓存行失效。这会来来回回的通过L3缓存,大大影响了性能。)
为了达到更好的线程可伸缩性,就必须确保不会有两个写线程操做同一个变量(多个读线程是没有问题的,如同处理器间调用高速连接获取缓存)。队列,它败在了独立写入原则(one-writer principle)。
若是两个不一样的线程写入队列中两个不一样的值,那么每一个内核都会使另一个线程的缓存行失效(数据在主内存与高速缓存之间的传输是作的固定大小的块传输,称之为缓存行。译者注:伪共享和缓存行)。尽管两个线程写入两个不一样的变量,也一样会引发它们间的资源竞争。这叫作伪共享,由于每次访问队列头(head),队列尾(tail)也一样会被加载到缓存行,反之亦然。
Disruptor 有一个基于数组的循环数据结构(环装缓冲区)。这个循环数据结构,它是个拥有下个可用元素引用的数组。预先分配了对象内存空间。生产者与消费者经过这个循环数据结构进行读写操做,并不会有锁或资源竞争。
在Disruptor 中,全部事件(events)以组播的方式被发布给全部消费者,以便下游队列经过并行的方式进行消费。由于消费者的并行消费,须要协调消费者间的依赖关系(依赖关系图)。
生产者和消费者中有个序列计数器,指示缓冲区中当前正在被它所处理的元素。全部生产者或消费者都只能够修改它本身的序列计数器,但同时能够读取其余的序列计数器。
让咱们把Disruptor 库的依赖关系添加到 pom.xml中。
<dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.6</version> </dependency>
最新版本的依赖关系能够在这里找到。
让咱们来定义一个携带数据的 Event:
public static class ValueEvent { private int value; public final static EventFactory EVENT_FACTORY = () -> new ValueEvent(); // standard getters and setters }
这个 EventFactory 会让 Disruptor分配事件。
消费者从环装缓冲区读取数据。让咱们来定义个处理事件的消费者:
public class SingleEventPrintConsumer { ... public EventHandler<ValueEvent>[] getEventHandler() { EventHandler<ValueEvent> eventHandler = (event, sequence, endOfBatch) -> print(event.getValue(), sequence); return new EventHandler[] { eventHandler }; } private void print(int id, long sequenceId) { logger.info("Id is " + id + " sequence id that was used is " + sequenceId); } }
在咱们的示例中,消费者只是打印打印日志。
构造 Disruptor:
ThreadFactory threadFactory = DaemonThreadFactory.INSTANCE; WaitStrategy waitStrategy = new BusySpinWaitStrategy(); Disruptor<ValueEvent> disruptor = new Disruptor<>( ValueEvent.EVENT_FACTORY, 16, threadFactory, ProducerType.SINGLE, waitStrategy);
在这个 Disruptor 的构造方法中,依次定义了如下参数:
链接消费者处理程序:
disruptor.handleEventsWith(getEventHandler());
Disruptor能够提供多个消费者来处理生产者生成的数据。在上面的例子中,咱们只使用了一个消费者处理事件。
RingBuffer<ValueEvent> ringBuffer = disruptor.start();
生产者将参数按顺序放置到环形缓冲区中。(译者注:3.4所述Event Factory已经做为参数,构造Disruptor对象)生产者必须获取到到下个可用元素,以免覆盖还没有消耗的元素。
利用 RingBuffer 发布事件:
for (int eventCount = 0; eventCount < 32; eventCount++) { long sequenceId = ringBuffer.next(); ValueEvent valueEvent = ringBuffer.get(sequenceId); valueEvent.setValue(eventCount); ringBuffer.publish(sequenceId); }
在此,生产者依次生产、发布事件。值得注意的是 Disruptor 与2阶段提交协议相似。它先获取一个新序列号(sequenceId),再经过(sequenceId)获取事件,而后制做事件,最后发布。下次得到sequenceId + 1。
在本教程中,咱们已经阐述了 Disruptor是什么,它是如何实现低延迟的并发处理。回顾了机械情怀的理念,以及如何利用它实现低延迟。最后展现了一个使用 Disruptor 库的例子。
示例代码能够在GitHub项目中找到。这是一个基于Maven的项目,因此它很容易导入和运行。
DDD CQRS架构和传统架构的优缺点比较
伪共享(False Sharing)
伪共享和缓存行
这次翻译拖了快两个月,纠结、消沉、迷离、回归。 开始以为不断的技术探索,仿佛只是对于前途的过多焦虑,让本身更多的沉浸于忙碌,从而更多的抬头看路。 看到不少人接下来的路,只是混混资历跟业务。而后慢慢的加薪拿股权,就算是人工智能其实也没有什么明朗的技术变现路线。 技术再好,也须要自我营销与宣传。止步眼前,心中颇多不甘。