工作中有个场景需要上报过滤、截断等数据,数据量级较大,需要将频繁写入的单条数据合并后进行批量上报。 技术选型中考虑可以采用disruptor框架实现相关功能。因此进行本篇分享。
Disruptor简介
是什么
Disruptor是一个开源的并发框架,该框架研发的初衷是为了解决高并发下列队锁的问题,最早由英国外汇交易公司LMAX(一种新型零售金融交易平台)开发并开源的,能够在无锁的情况下实现队列的高并发操作。LMAX平台使用该框架对订单处理速度能达到600万TPS,除金融领域之外,其他一般的应用中都可以用到Disruptor,它可以带来显著的性能提升。其中业界开源组件使用Disruptor的包括Log4j2、Apache Storm等等。
适用的场景
Disruptor 它可以用来作为高性能的有界内存队列, 适用于两大场景:
- 生产者消费者场景
- 细分场景
- 单生产者多消费者场景
- 多生产者单消费者场景
- 单生产者多消费者场景
- 细分场景
- 发布订阅 场景
- Disruptor也可以认为是观察者模式的一种实现,实现发布订阅模式。
为什么需要Disruptor
Java中的队列
在Java中的java.util.concurrent包下有大量的队列供我们使用:
- ArrayBlockingQueue
- 基于数组结构的队列,通过加锁的方式,来保证多线程情况下数据的安全;
- LinkedBlockingQueue
- 基于链表结构的队列,也通过加锁的方式,来保证多线程情况下数据的安全;
- ConcurrentLinkedQueue
- 基于链表结构的队列,通过compare and swap(简称CAS)的方式,来保证多线程情况下数据的安全,不加锁,主要使用了Java中的sun.misc.Unsafe类来实现。
- LinkedTransferQueue
- 类似ConcurrentLinkedQueue。
Java中的队列主要有如下特点
- 使用加锁实现队列的类,虽然是有界的(可以设置队列的大小),但是有锁的存在,性能上有了很大的影响,线程由于锁的竞争被挂起,直到锁的释放,才能恢复。
- 使用CAS实现队列的类,都是无界的,无法保证队列的长度,理论上来说可以是无限扩展,那么如果生产者生产过快,消费者还没来得及消费,最终可能会导致内存溢出,影响系统稳定;
Disruptor解决了以上的问题,实现了有界无锁队列操作。
Diruptor基本概念
- RingBuffer——Disruptor底层数据结构实现,核心类,是线程间交换数据的中转地;
- Sequencer——序号管理器,生产同步的实现者,负责消费者/生产者各自序号、序号栅栏的管理和协调,Sequencer有单生产者,多生产者两种不同的模式,里面实现了各种同步的算法;
- Sequence——序号,声明一个序号,用于跟踪ringbuffer中任务的变化和消费者的消费情况,disruptor里面大部分的并发代码都是通过对Sequence的值同步修改实现的,而非锁,这是disruptor高性能的一个主要原因;
- SequenceBarrier——序号栅栏,管理和协调生产者的游标序号和各个消费者的序号,确保生产者不会覆盖消费者未来得及处理的消息,确保存在依赖的消费者之间能够按照正确的顺序处理
- EventProcessor——事件处理器,监听RingBuffer的事件,并消费可用事件,从RingBuffer读取的事件会交由实际的生产者实现类来消费;它会一直侦听下一个可用的序号,直到该序号对应的事件已经准备好。
- EventHandler——业务处理器,是实际消费者的接口,完成具体的业务逻辑实现,第三方实现该接口;代表着消费者。
- Producer——生产者接口,第三方线程充当该角色,producer向RingBuffer写入事件。
- Wait Strategy:Wait Strategy决定了一个消费者怎么等待生产者将事件(Event)放入Disruptor中。
| 策略 | 描述 |
|---|---|
| 「BlockingWaitStrategy」 | Disruptor的默认策略是BlockingWaitStrategy。在BlockingWaitStrategy内部是使用锁和condition来控制线程的唤醒。BlockingWaitStrategy是最低效的策略,但其对CPU的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现。 |
| 「SleepingWaitStrategy」 | SleepingWaitStrategy 的性能表现跟 BlockingWaitStrategy 差不多,对 CPU 的消耗也类似,但其对生产者线程的影响最小,通过使用LockSupport.parkNanos(1)来实现循环等待。 |
| 「YieldingWaitStrategy」 | YieldingWaitStrategy是可以使用在低延迟系统的策略之一。YieldingWaitStrategy将自旋以等待序列增加到适当的值。在循环体内,将调用Thread.yield()以允许其他排队的线程运行。在要求极高性能且事件处理线数小于 CPU 逻辑核心数的场景中,推荐使用此策略; |
| 「BusySpinWaitStrategy」 | 性能最好,适合用于低延迟的系统。在要求极高性能且事件处理线程数小于CPU逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。 |
| 「PhasedBackoffWaitStrategy」 | 自旋 + yield + 自定义策略,CPU资源紧缺,吞吐量和延迟并不重要的场景。 |
Diruptor极速体验
单生产者多消费者示例
1.创建代表数据的bean
private static class PCData {
private long value;
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
}
2.创建工厂类,用于生产对象
private static class PCDataFactory implements EventFactory<PCData> {
public PCData newInstance() {
return new PCData();
}
}
3.创建消费者
private static class Consumer implements WorkHandler<PCData> {
public void onEvent(PCData event) throws Exception {
System.out.println(Thread.currentThread().getId() + ":Event: --" + event.getValue() + "--");
Thread.sleep(100);
}
}
4.创建生产者
private static class Producer {
private final RingBuffer<PCData> ringBuffer;
public Producer(RingBuffer<PCData> ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void pushData(ByteBuffer bb) {
long sequence = ringBuffer.next();
try {
PCData event = ringBuffer.get(sequence);
event.setValue(bb.getLong(0));
} finally {
ringBuffer.publish(sequence);
}
}
}
5.核心逻辑
public class DisruptorPC {
public static void main(String[] args) {
PCDataFactory factory = new PCDataFactory();
int bufferSize = 8;
ExecutorService executor = Executors.newCachedThreadPool();
Disruptor<PCData> disruptor = new Disruptor<PCData>(factory, bufferSize, executor, ProducerType.SINGLE, new BlockingWaitStrategy());
disruptor.handleEventsWithWorkerPool(new Consumer(), new Consumer(), new Consumer(), new Consumer());
disruptor.start();
RingBuffer<PCData> ringBuffer = disruptor.getRingBuffer();
Producer producer = new Producer(ringBuffer);
ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++) {
bb.putLong(0, l);
producer.pushData(bb);
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("add data " + l);
// break;
}
// disruptor.shutdown();
// executor.shutdown();
}
}
打印结果:
11:Event: --0--
add data 0
add data 1
14:Event: --1--
add data 2
12:Event: --2--
13:Event: --3--
add data 3
add data 4
add data 5
add data 6
add data 7
13:Event: --4--
12:Event: --7--
add data 8
多生产者多消费者示例
1.Event对象
@Data
public class Order {
private String id;
}
2.消费者
public class Consumer implements WorkHandler<Order> {
private String consumerId;
private static AtomicInteger count = new AtomicInteger(0);
private Random random = new Random();
public Consumer(String consumerId) {
this.consumerId = consumerId;
}
@Override
public void onEvent(Order order) throws Exception {
Thread.sleep(1 * random.nextInt(5));
System.err.println("当前消费者: " + this.consumerId + ", 消费信息ID: " + order.getId());
count.incrementAndGet();
}
public int getCount(){
return count.get();
}
}
3.生产者
public class Producer {
private RingBuffer<Order> ringBuffer;
public Producer(RingBuffer<Order> ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void sendData(String uuid) {
long sequence = ringBuffer.next();
try {
Order order = ringBuffer.get(sequence);
order.setId(uuid);
} finally {
ringBuffer.publish(sequence);
}
}
}
4.执行逻辑
public class DisruptorCase {
public static void main(String[] args) {
//1 创建RingBuffer
RingBuffer<Order> ringBuffer =
RingBuffer.create(ProducerType.MULTI,
new EventFactory<Order>() {
public Order newInstance() {
return new Order();
}
},
1024 * 1024,
new YieldingWaitStrategy());
//2 通过ringBuffer 创建一个屏障
SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();
//3 创建含有10个消费者的数组:
Consumer[] consumers = new Consumer[10];
for(int i = 0; i < consumers.length; i++) {
consumers[i] = new Consumer("C" + i);
}
//4 构建多消费者工作池
WorkerPool<Order> workerPool = new WorkerPool<Order>(
ringBuffer,
sequenceBarrier,
new EventExceptionHandler(),
consumers);
//5 设置多个消费者的sequence序号 用于单独统计消费进度, 并且设置到ringbuffer中
ringBuffer.addGatingSequences(workerPool.getWorkerSequences());
//6 启动workerPool
workerPool
.start(Executors.newFixedThreadPool(5));
CyclicBarrier barrier = new CyclicBarrier(100);
for(int i = 0; i < 100; i++) {
final Producer producer = new Producer(ringBuffer);
new Thread(new Runnable() {
public void run() {
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
for(int j = 0; j < 100; j++) {
producer.sendData(UUID.randomUUID().toString());
}
}
}).start();
}
System.err.println("----------线程创建完毕,开始生产数据----------");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.err.println("任务总数:" + consumers[2].getCount());
}
static class EventExceptionHandler implements ExceptionHandler {
public void handleEventException(Throwable throwable, long l, Order o) {
}
@Override
public void handleEventException(Throwable throwable, long l, Object o) {
}
public void handleOnStartException(Throwable ex) {
}
public void handleOnShutdownException(Throwable ex) {
}
}
}
控制台结果:
当前消费者: C0, 消费信息ID: b3f2ce69-676c-4ed0-8c75-f443dde6450f
当前消费者: C2, 消费信息ID: 84a54e4a-c9da-4e1b-ac07-41bf034394d1
当前消费者: C1, 消费信息ID: cc19c109-7a24-4987-a22e-e4b0ba320bbb
当前消费者: C4, 消费信息ID: 0c807d7a-5fec-43c8-938c-452e8c643aeb
当前消费者: C0, 消费信息ID: 3dd88a39-68bf-4e2f-8c94-0cbc1b7f61e0
当前消费者: C3, 消费信息ID: f891fdad-b4d4-4896-8427-38f6b385cdfd
当前消费者: C1, 消费信息ID: 23cdfd88-f2fe-41db-90a1-f945647aced8
当前消费者: C4, 消费信息ID: 434ba60e-2f04-4206-8a11-3a99c517a3fe
当前消费者: C2, 消费信息ID: af701386-3f08-4b00-bb24-65254550bb05
任务总数:10000
Disruptor源码解读
以单生产者多消费者示例进行源码解读。
Disruptor初始化
用例中创建建Disruptor的代码如下:
PCDataFactory factory = new PCDataFactory();
int bufferSize = 8;
ExecutorService executor = Executors.newCachedThreadPool();
Disruptor<PCData> disruptor = new Disruptor<PCData>(factory,
bufferSize,
executor,
ProducerType.SINGLE,
new BlockingWaitStrategy());
Disruptor构建流程:
public Disruptor(
final EventFactory<T> eventFactory,
final int ringBufferSize,
final Executor executor,
final ProducerType producerType,
final WaitStrategy waitStrategy)
{
// Build Disruptor
this(
// Build RingBuffer
RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), executor);
}
首先是创建了RingBuffer对象,然后接着构造了Disruptor对象。
- RingBuffer的创建
public static <E> RingBuffer<E> create(
ProducerType producerType, // 生产者模式(SINGLE:单生产者模式,MULTI:多生产者模式)
EventFactory<E> factory, // Event的工厂类
int bufferSize, // RingBuffer中元素大小
WaitStrategy waitStrategy) // 消费者等待策略
{
switch (producerType)
{
case SINGLE:
// 单生产者模式
return createSingleProducer(factory, bufferSize, waitStrategy);
case MULTI:
// 多生产者模式
return createMultiProducer(factory, bufferSize, waitStrategy);
default:
throw new IllegalStateException(producerType.toString());
}
}
以用例中单生产者为例,查看createSingleProducer方法。
public static <E> RingBuffer<E> createSingleProducer(
EventFactory<E> factory, // Event的工厂类
int bufferSize, // RingBuffer中元素大小
WaitStrategy waitStrategy) // 消费者等待策略
{
SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy);
return new RingBuffer<E>(factory, sequencer);
}
createSingleProducer 方法主要是创建了 SingleProducerSequencer 和创建 RingBuffer 。
SingleProducerSequencer构建方法中,多级父类继承定义域属性:
/**
* 单生产者的缓存行填充 避免 {@link SingleProducerSequencerFields#nextValue}、{@link SingleProducerSequencerFields#cachedValue}
* 与无关对象产生伪共享。
*/
public final class SingleProducerSequencer extends SingleProducerSequencerFields
{
/**
* 缓冲行填充,保护 {@link SingleProducerSequencerFields#nextValue}、{@link SingleProducerSequencerFields#cachedValue}
*/
protected long p1, p2, p3, p4, p5, p6, p7;
/**
* Construct a Sequencer with the selected wait strategy and buffer size.
*
* @param bufferSize the size of the buffer that this will sequence over.
* @param waitStrategy for those waiting on sequences.
*/
public SingleProducerSequencer(int bufferSize, WaitStrategy waitStrategy)
{
super(bufferSize, waitStrategy);
}
}
/**
* 单线程的序号生成器
*
* <p>Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s.
* Not safe for use from multiple threads as it does not implement any barriers.</p>
*
* <p>* Note on {@link Sequencer#getCursor()}: With this sequencer the cursor value is updated after the call
* to {@link Sequencer#publish(long)} is made.</p>
*/
abstract class SingleProducerSequencerFields extends SingleProducerSequencerPad
{
SingleProducerSequencerFields(int bufferSize, WaitStrategy waitStrategy)
{
super(bufferSize, waitStrategy);
}
}
/**
* 单生产者的缓存行填充 避免 {@link SingleProducerSequencerFields#nextValue}、{@link SingleProducerSequencerFields#cachedValue}
* 与无关对象产生伪共享。
*/
abstract class SingleProducerSequencerPad extends AbstractSequencer
{
/**
* 已预分配的缓存,因为是单线程的生产者,不存在竞争,因此采用普通的long变量
* 表示 {@link #cursor} +1 ~ nextValue 这段空间被预分配出去了,但是可能还未填充数据。
* <p>
* 会在真正分配空间时更新
* {@link SingleProducerSequencer#tryNext(int)}
* {@link SingleProducerSequencer#next(int)}
* <p>
* 这个名字其实有点坑爹,其实不是下一个value。
*
* Set to -1 as sequence starting point
*/
long nextValue = Sequence.INITIAL_VALUE;
/**
* 网关序列的最小序号缓存。
* 因为是单线程的生产者,数据无竞争,因此使用普通的long变量即可。
*
* 在运行期间不调用{@link #claim(long)}的情况下:
* 1.该缓存值是单调递增的,只会变大不会变小 2. cachedValue <= nextValue
* 如果在运行期间调用了{@link #claim(long)}
* 可能造成cachedValue > nextValue
*
* <p>
* Q: 该缓存值的作用?
* A: 除了直观上的减少对{@link #gatingSequences}的遍历产生的volatile读以外,还可以提高缓存命中率。
* <p>
* 由于消费者的{@link Sequence}变更较为频繁,因此消费者的{@link Sequence}的缓存极易失效。
* 如果生产者频繁读取消费者的{@link Sequence},极易遇见缓存失效问题(伪共享),从而影响性能。
* 通过缓存一个值(在必要的时候更新),可以极大的减少对消费者的{@link Sequence}的读操作,从而提高性能。
* PS: 使用一个变化频率较低的值代替一个变化频率较高的值,提高读效率。
*
* 在每次查询消费者的进度后,就会对它进行缓存
* 会在{@link SingleProducerSequencer#hasAvailableCapacity(int, boolean)}
* {@link SingleProducerSequencer#tryNext(int)}
* {@link SingleProducerSequencer#next(int)}
*
* {@link Util#getMinimumSequence(Sequence[], long)}
*/
long cachedValue = Sequence.INITIAL_VALUE;
SingleProducerSequencerPad(int bufferSize, WaitStrategy waitStrategy)
{
super(bufferSize, waitStrategy);
}
}
/**
* 抽象序号生成器,作为单生产者和多生产者序列号生成器的超类,实现一些公共的功能(添加删除gatingSequence)。
*
* Base class for the various sequencer types (single/multi). Provides
* common functionality like the management of gating sequences (add/remove) and
* ownership of the current cursor.
*/
public abstract class AbstractSequencer implements Sequencer
{
/**
* 原子方式更新 追踪的Sequences
*/
private static final AtomicReferenceFieldUpdater<AbstractSequencer, Sequence[]> SEQUENCE_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(AbstractSequencer.class, Sequence[].class, "gatingSequences");
/**
* 序号生成器缓冲区大小(ringBuffer有效数据缓冲区大小)
*/
protected final int bufferSize;
/**
* 消费者的等待策略。
* 为何放在这里?因为SequenceBarrier由Sequencer创建,Barrier需要生产者的Sequence信息。
*/
protected final WaitStrategy waitStrategy;
/**
* 生产者的序列,表示生产者的进度。
* PS: 代码里面的带cursor的都表示生产者们的Sequence。
* <p>
* 消费者与生产者之间的交互(可见性保证)是通过volatile变量的读写来保证的。
* 消费者们观察生产者的进度,当看见生产者进度增大时,生产者这期间的操作对消费者来说都是可见的。
* volatile的happens-before原则-----生产者的进度变大(写volatile)先于消费者看见它变大(读volatile)。
* 在多生产者情况下,只能看见空间分配操作,要确定哪些数据发布还需要额外保证.
* {@link #getHighestPublishedSequence(long, long)}
*/
protected final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);
/**
* 网关Sequences,序号生成器必须和这些Sequence满足约束:
* cursor-bufferSize <= Min(gatingSequence)
* 即:所有的gatingSequences让出下一个插槽后,生产者才能获取该插槽。
* <p>
* 对于生产者来讲,它只需要关注消费链最末端的消费者的进度(因为它们的进度是最慢的)。
* 即:gatingSequences就是所有消费链末端的消费们所拥有的的Sequence。(想一想食物链)
* <p>
* 类似{@link ProcessingSequenceBarrier#cursorSequence}
*/
protected volatile Sequence[] gatingSequences = new Sequence[0];
public AbstractSequencer(int bufferSize, WaitStrategy waitStrategy)
{
if (bufferSize < 1)
{
throw new IllegalArgumentException("bufferSize must not be less than 1");
}
if (Integer.bitCount(bufferSize) != 1)
{
throw new IllegalArgumentException("bufferSize must be a power of 2");
}
this.bufferSize = bufferSize;
this.waitStrategy = waitStrategy;
}
}
/**
* 序号生成器。
* 生产者们通过该对象发布可用的序号,消费者们通过该对象查询可用的序号。
*
* Coordinates claiming sequences for access to a data structure while tracking dependent {@link Sequence}s
*/
public interface Sequencer extends Cursored, Sequenced
{
/**
* 将-1作为默认序号
* Set to -1 as sequence starting point
*/
long INITIAL_CURSOR_VALUE = -1L;
}
SingleProducerSequencer 主要定义了如下域属性:
- cursor:生产者的序列,表示生产者的进度
- gatingSequences:网关Sequences,所有消费链末端的消费们所拥有的的Sequence。
- BufferSize:序号生成器缓冲区大小(ringBuffer有效数据缓冲区大小)
- waitStrategy:消费者的等待策略
- nextValue:已预分配的缓存
- cachedValue:网关序列的最小序号缓存
RingBuffer的构建:
public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E>
{
public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE;
protected long p1, p2, p3, p4, p5, p6, p7;
/**
* Construct a RingBuffer with the full option set.
*
* @param eventFactory to newInstance entries for filling the RingBuffer
* @param sequencer sequencer to handle the ordering of events moving through the RingBuffer.
* @throws IllegalArgumentException if bufferSize is less than 1 or not a power of 2
*/
RingBuffer(
EventFactory<E> eventFactory,
Sequencer sequencer)
{
super(eventFactory, sequencer);
}
}
abstract class RingBufferFields<E> extends RingBufferPad
{
// 数组填充大小,避免数组的有效元素出现伪共享
// 数组的前X个元素会出现伪共享和后X个元素可能会出现伪共享,可能和无关数据加载到同一个缓存行。
// (更多伪共享信息请查阅资料)
private static final int BUFFER_PAD;
// 引用对象数组有效首元素地址偏移量(这里因为进行了填充,所以不是真正的首元素的地址偏移量)
private static final long REF_ARRAY_BASE;
// 引用对象数组的单个元素的地址移位量(用移位运算代替乘法)
private static final int REF_ELEMENT_SHIFT;
private static final Unsafe UNSAFE = Util.getUnsafe();
static
{
final int scale = UNSAFE.arrayIndexScale(Object[].class);
if (4 == scale)
{
REF_ELEMENT_SHIFT = 2;
}
else if (8 == scale)
{
REF_ELEMENT_SHIFT = 3;
}
else
{
throw new IllegalStateException("Unknown pointer size");
}
// 这里前后各填充了128字节,和Disruptor的其它设计并不一致,其它地方都是64字节,当然128兼容64
BUFFER_PAD = 128 / scale;
// Q: 这里是什么意思?
// A: 因为数组的前后我们都进行了填充,以避免有效载荷和其它成员产生伪共享,
// 因此我们需要在数组的起始偏移量上,再加上我们填充的那一段,才是我们有效载荷的起始偏移量
// Including the buffer pad in the array base offset
REF_ARRAY_BASE = UNSAFE.arrayBaseOffset(Object[].class) + (BUFFER_PAD << REF_ELEMENT_SHIFT);
}
/**
* 索引掩码,表示后X位是有效数字(截断)。位运算代替取余快速计算插槽索引
*/
private final long indexMask;
/**
* 事件对象数组,大于真正需要的容量,采用了缓存行填充减少伪共享。
*/
private final Object[] entries;
/**
* 缓存有效空间大小(必须是2的整次幂,-1就是掩码)
*/
protected final int bufferSize;
/**
* 序号生成器
*/
protected final Sequencer sequencer;
RingBufferFields(
EventFactory<E> eventFactory,
Sequencer sequencer)
{
this.sequencer = sequencer;
this.bufferSize = sequencer.getBufferSize();
if (bufferSize < 1)
{
throw new IllegalArgumentException("bufferSize must not be less than 1");
}
// RingBuffer大小必须要2^n
if (Integer.bitCount(bufferSize) != 1)
{
throw new IllegalArgumentException("bufferSize must be a power of 2");
}
// 掩码
this.indexMask = bufferSize - 1;
// 额外创建 2个填充空间的大小,首尾填充,避免数组的有效载荷和其它成员加载到同一缓存行。
this.entries = new Object[sequencer.getBufferSize() + 2 * BUFFER_PAD];
fill(eventFactory);
}
/**
* 数组元素的预填充
*/
private void fill(EventFactory<E> eventFactory)
{
for (int i = 0; i < bufferSize; i++)
{
// BUFFER_PAD + i为真正的数组索引
entries[BUFFER_PAD + i] = eventFactory.newInstance();
}
}
}
abstract class RingBufferPad
{
protected long p1, p2, p3, p4, p5, p6, p7;
}
RingBuffer创建流程主要是在RingBufferFields的构建方法中完成的 ,流程为:赋值生产者sequencer、赋值RingBuffer大小,然后 填充数组元素,其中头尾都填充空余元素,防止伪共享。
- 构建Disruptor
public class Disruptor<T>
{
private final RingBuffer<T> ringBuffer;
/**
* 为消费者创建线程用。
* 查看{@link BasicExecutor}以避免死锁问题。
*/
private final Executor executor;
/**
* 消费者信息仓库
*/
private final ConsumerRepository<T> consumerRepository = new ConsumerRepository<>();
/**
* 运行状态标记
*/
private final AtomicBoolean started = new AtomicBoolean(false);
/**
* EventHandler的异常处理器。
* 警告!!!默认的异常处理器在EventHandler抛出异常时会终止EventProcessor的线程(退出任务),可能导致死锁。
*/
private ExceptionHandler<? super T> exceptionHandler = new ExceptionHandlerWrapper<>();
public Disruptor(
final EventFactory<T> eventFactory,
final int ringBufferSize,
final Executor executor,
final ProducerType producerType,
final WaitStrategy waitStrategy)
{
// Build Disruptor
this(
// Build RingBuffer
RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), executor);
}
/**
* Private constructor helper
*/
private Disruptor(final RingBuffer<T> ringBuffer, final Executor executor)
{
this.ringBuffer = ringBuffer;
this.executor = executor;
}
}
以上是Disruptor的构建过程,下面介绍消费方式流程。
添加消费方式流程
示例代码:
disruptor.handleEventsWithWorkerPool(new Consumer(), new Consumer(), new Consumer(), new Consumer());
handleEventsWithWorkerPool内部逻辑:
/**
* 添加一个多线程的消费者。该消费者会将事件分发到各个WorkHandler。每一个事件只会被其中一个WorkHandler处理。
*/
@SafeVarargs
@SuppressWarnings("varargs")
public final EventHandlerGroup<T> handleEventsWithWorkerPool(final WorkHandler<T>... workHandlers)
{
return createWorkerPool(new Sequence[0], workHandlers);
}
createWorkerPool内部流程:
/**
* 创建一个多线程的消费者,消费者的线程数取决于workHandler的数量
*
* @param barrierSequences WorkPool消费者的所有前驱节点的序列,作为新消费者的依赖序列
* @param workHandlers 线程池中处理事件的单元,每一个都会包装为{@link com.lmax.disruptor.WorkProcessor},
* 数组长度决定线程的数量。
* 如果这些语法觉得有点奇怪的是正常的,为了更好的灵活性,比传数量会好一些
*/
EventHandlerGroup<T> createWorkerPool(
final Sequence[] barrierSequences, final WorkHandler<? super T>[] workHandlers)
{
// 下方描述newBarrier的内部逻辑,见讲解1
final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(barrierSequences);
final WorkerPool<T> workerPool = new WorkerPool<>(ringBuffer, sequenceBarrier, exceptionHandler, workHandlers);
// 添加sequenceBarrier到consumerRepository中,见讲解2
consumerRepository.add(workerPool, sequenceBarrier);
// 将Sequence添加到RingBuffer的GatingSequences中,见讲解3
final Sequence[] workerSequences = workerPool.getWorkerSequences();
updateGatingSequencesForNextInChain(barrierSequences, workerSequences);
return new EventHandlerGroup<>(this, consumerRepository, workerSequences);
}
createEventProcessors 主要做了以下3件事:a) 为每个消费者设置了一 个 Sequence ; b) 添加消费者到 Disruptor 的将消费者的consumerInfos ; c) 将Sequence添加到RingBuffer的GatingSequences中。
1.讲解1,ringBuffer.newBarrier的流程
public SequenceBarrier newBarrier(Sequence... sequencesToTrack)
{
return sequencer.newBarrier(sequencesToTrack);
}
public abstract class AbstractSequencer implements Sequencer
{
public SequenceBarrier newBarrier(Sequence... sequencesToTrack)
{
return new ProcessingSequenceBarrier(this, waitStrategy, cursor, sequencesToTrack);
}
}
/**
* 消费者使用的事件处理器序号屏障
* {@link SequenceBarrier} handed out for gating {@link EventProcessor}s on a cursor sequence and optional dependent {@link EventProcessor}(s),
* using the given WaitStrategy.
*/
final class ProcessingSequenceBarrier implements SequenceBarrier
{
/**
* 消费者的等待策略
*/
private final WaitStrategy waitStrategy;
/**
* 依赖的Sequence。
* EventProcessor(事件处理器)的Sequence必须小于等于依赖的Sequence
* 来自于{@link com.lmax.disruptor.dsl.EventHandlerGroup#sequences}
*
* 对于直接和Sequencer相连的消费者,它依赖的Sequence就是Sequencer的Sequence。
* 对于跟在其它消费者屁股后面的消费者,它依赖的Sequence就是它跟随的所有消费者的Sequence。
*
* 类似 {@link AbstractSequencer#gatingSequences}
* dependentSequence
*/
private final Sequence dependentSequence;
/**
* 是否请求了关闭消费者
*/
private volatile boolean alerted = false;
/**
* 生产者的进度(cursor)
* 依赖该屏障的事件处理器的进度【必然要小于等于】生产者的进度
*/
private final Sequence cursorSequence;
/**
* 序号生成器(来自生产者)
*/
private final Sequencer sequencer;
ProcessingSequenceBarrier(
final Sequencer sequencer,
final WaitStrategy waitStrategy,
final Sequence cursorSequence,
final Sequence[] dependentSequences)
{
this.sequencer = sequencer;
this.waitStrategy = waitStrategy;
this.cursorSequence = cursorSequence;
// 如果消费者不依赖于其它的消费者,那么只需要与生产者的进度进行协调
if (0 == dependentSequences.length)
{
dependentSequence = cursorSequence;
}
else
{
// 如果依赖于其它消费者,那么追踪其它消费者的进度。
dependentSequence = new FixedSequenceGroup(dependentSequences);
}
}
}
2.讲解2,添加sequenceBarrier到consumerRepository中:
/**
* 消费者信息仓库,将EventHandler 与 EventProcessor关联起来。
* 传递给Disruptor的每一个EventHandler最终都会关联到一个EventProcessor。
* <P>
* 消费者之间的可见性保证:
* Sequence是单调递增的,当看见前驱消费者的进度增大时,所有前驱消费者对区间段内的数据的处理对当前消费者来说都是可见的。
* volatile的happens-before原则-----前驱消费者们的进度变大(写volatile)先于我看见它变大(读volatile)。
*
* Provides a repository mechanism to associate {@link EventHandler}s with {@link EventProcessor}s
* @param <T> the type of the {@link EventHandler}
*/
class ConsumerRepository<T> implements Iterable<ConsumerInfo>
{
/**
* EventHandler到批量事件处理消费者信息的映射,用于信息查询
*/
private final Map<EventHandler<?>, EventProcessorInfo<T>> eventProcessorInfoByEventHandler =
new IdentityHashMap<>();
/**
* Sequence到消费者信息的映射
* 一个消费者可能有多个Sequence{@link WorkerPool},但是一个Sequence只从属一个消费者。
*/
private final Map<Sequence, ConsumerInfo> eventProcessorInfoBySequence =
new IdentityHashMap<>();
/**
* 消费者信息列表
*/
private final Collection<ConsumerInfo> consumerInfos = new ArrayList<>();
/**
* 添加一个多事件处理器的消费者(WorkerPool代表一个多线程的消费者)
*/
public void add(final WorkerPool<T> workerPool, final SequenceBarrier sequenceBarrier)
{
final WorkerPoolInfo<T> workerPoolInfo = new WorkerPoolInfo<>(workerPool, sequenceBarrier);
consumerInfos.add(workerPoolInfo);
for (Sequence sequence : workerPool.getWorkerSequences())
{
eventProcessorInfoBySequence.put(sequence, workerPoolInfo);
}
}
}
这里主要是用来存储消费者信息,有两个维度的Map。
3.讲解3,Sequence添加到RingBuffer的GatingSequences流程
public class Disruptor<T>{
/**
* 更新网关序列(当消费者链后端添加新节点时)
* @param barrierSequences 新节点依赖的网关序列(屏障序列),这些序列有了后继节点,就不再是网关序列了
* @param processorSequences 新增加的节点的序列,新增的在消费者链的末端,因此它们的序列就是新增的网关序列
*/
private void updateGatingSequencesForNextInChain(final Sequence[] barrierSequences, final Sequence[] processorSequences)
{
if (processorSequences.length > 0)
{
// 将新增加的消费者节点序列添加到网关序列中
ringBuffer.addGatingSequences(processorSequences);
// 移除新节点的网关序列,这些序列有了后继节点,就不再是网关序列了
for (final Sequence barrierSequence : barrierSequences)
{
ringBuffer.removeGatingSequence(barrierSequence);
}
// 将这些序列标记为不再是消费者链的最后节点
consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences);
}
}
}
这段代码总共做了三件事:a) 把新的消费者游标(Sequence)添加到 RingBuffer 的 GatingSequences 中;b) 把新消费者依赖的消费者游标(Sequence)从 RingBuffer 的 GatingSequences 中移除;c) 把被依赖的消费者的endOfChain属性设置成false。
Disruptor启动流程
public RingBuffer<T> start()
{
checkOnlyStartedOnce();
for (final ConsumerInfo consumerInfo : consumerRepository)
{
consumerInfo.start(executor);
}
return ringBuffer;
}
/**
* 确保disruptor只启动一次
*/
private void checkOnlyStartedOnce()
{
if (!started.compareAndSet(false, true))
{
throw new IllegalStateException("Disruptor.start() must only be called once.");
}
}
Disruptor 的启动就是启动所有的 ConsumerInfo 。下面看看 ConsumerInfo 的启动流程:
public void start(Executor executor)
{
workerPool.start(executor);
}
/**
* Start the worker pool processing events in sequence.
*
* @param executor providing threads for running the workers.
* @return the {@link RingBuffer} used for the work queue.
* @throws IllegalStateException if the pool has already been started and not halted yet
*/
public RingBuffer<T> start(final Executor executor)
{
if (!started.compareAndSet(false, true))
{
throw new IllegalStateException("WorkerPool has already been started and cannot be restarted until halted.");
}
// 获取指针
final long cursor = ringBuffer.getCursor();
workSequence.set(cursor);
for (WorkProcessor<?> processor : workProcessors)
{
processor.getSequence().set(cursor);
executor.execute(processor);
}
return ringBuffer;
}
WorkProcessor中实际execute的逻辑:
public final class WorkProcessor<T> implements EventProcessor{
public void run()
{
// CAS操作保证只有一个线程能运行,和保证可见性(看见状态为false后于前一个线程将其设置为false)
if (!running.compareAndSet(false, true))
{
throw new IllegalStateException("Thread is already running");
}
// 清除特定状态(可理解为清除线程的中断状态)
sequenceBarrier.clearAlert();
notifyStart();
// 是否处理了一个事件。在处理完一个事件之后会再次竞争序号进行消费
boolean processedSequence = true;
// 看见的已发布序号的缓存,这里是局部变量,在该变量上无竞争
long cachedAvailableSequence = Long.MIN_VALUE;
// 下一个要消费的序号(要消费的事件编号),注意起始为-1 ,注意与BatchEventProcessor的区别
// BatchEventProcessor初始值为 sequence.get()+1
// 存为local variable 还减少大量的volatile变量读,且保证本次操作过程中的一致性
long nextSequence = sequence.get();
// 要消费的事件对象
T event = null;
while (true)
{
try
{
// 如果前一个事情被成功处理了--拉取下一个序号,并将上一个序号标记为已成功处理。
// 一般来说,这都是正确的。
// 这可以防止当workHandler抛出异常时,Sequence跨度太大。
// if previous sequence was processed - fetch the next sequence and set
// that we have successfully processed the previous sequence
// typically, this will be true
// this prevents the sequence getting too far forward if an exception
// is thrown from the WorkHandler
if (processedSequence)
{
processedSequence = false;
do
{
// 获取workProcessor所属的消费者的进度,与workSequence同步(感知其他消费者的进度)
nextSequence = workSequence.get() + 1L;
sequence.set(nextSequence - 1L);
}
while (!workSequence.compareAndSet(nextSequence - 1L, nextSequence));
// CAS更新workSequence的序号(预分配序号),为什么这样是安全的呢?
// 由于消费者的进度由最小的Sequence决定,当它CAS更新workSequence之后,它代替了workSequence处在旧的进度上。
// 就算多个workProcessor竞争,总有一个是处在正确的进度上的。因此 workSequence 的更新并不会影响WorkerPool代表的消费者的消费进度。
}
// 它只能保证竞争到的序号是可用的,因此只能只消费一个。
// 而BatchEventProcessor看见的所有序号都是可用的
if (cachedAvailableSequence >= nextSequence)
{
event = ringBuffer.get(nextSequence);
workHandler.onEvent(event);
processedSequence = true;
}
else
{
// 等待生产者进行生产,这里和BatchEventProcessor不同,
// 如果waitFor抛出TimeoutException、Throwable以外的异常,那么cachedAvailableSequence不会被更新,
// 也就不会导致nextSequence被标记为已消费!
cachedAvailableSequence = sequenceBarrier.waitFor(nextSequence);
}
}
catch (final TimeoutException e)
{
notifyTimeout(sequence.get());
}
catch (final AlertException ex)
{
if (!running.get())
{
break;
}
}
catch (final Throwable ex)
{
// handle, mark as processed, unless the exception handler threw an exception
// 同样的警告!如果在处理异常时抛出新的异常,会导致跳出while循环,导致WorkProcessor停止工作,可能导致死锁
// 而系统默认的异常处理会将其包装为RuntimeException!!!
exceptionHandler.handleEventException(ex, nextSequence, event);
// 成功处理异常后标记当前事件已被消费
processedSequence = true;
}
}
notifyShutdown();
// 写volatile,插入StoreLoad屏障,保证其他线程能看见我退出前的所有操作
running.set(false);
}
}
Disruptor 的启动流程主要就是启动消费者,开始消费数据。 消费逻辑就是从 Barrier 中获取可以消费的位点开始消费,关于sequenceBarrier.waitFor 方法放到等待策略中讲解。
生产者发布数据流程
生产者发布数据流程为:获取下一个发布位点,然后填充数据发布:
private static class Producer {
private final RingBuffer<PCData> ringBuffer;
public Producer(RingBuffer<PCData> ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void pushData(ByteBuffer bb) {
long sequence = ringBuffer.next();
try {
PCData event = ringBuffer.get(sequence);
event.setValue(bb.getLong(0));
} finally {
ringBuffer.publish(sequence);
}
}
}
- RingBuffer.next()方法
public long next()
{
return sequencer.next();
}
public final class SingleProducerSequencer extends SingleProducerSequencerFields {
public long next(int n)
{
if (n < 1)
{
throw new IllegalArgumentException("n must be > 0");
}
// 已分配的序号的缓存(已分配到这里),初始-1
long nextValue = this.nextValue;
// 本次申请分配的序号
long nextSequence = nextValue + n;
// nextSequence在上一圈的点位
// 构成环路的点:环形缓冲区可能追尾的点 = 等于本次申请的序号-环形缓冲区大小
// 如果该序号大于最慢消费者的进度,那么表示追尾了,需要等待
long wrapPoint = nextSequence - bufferSize;
// 上次缓存的最小网关序号(消费最慢的消费者的进度)
long cachedGatingSequence = this.cachedValue;
// wrapPoint > cachedGatingSequence 表示生产者追上消费者产生环路(追尾),即缓冲区已满,此时需要获取消费者们最新的进度,以确定是否队列满
// cachedGatingSequence > nextValue 表示消费者的进度大于生产者进度,nextValue无效,建议忽略,
// 正常情况下不会出现,调用claim(long)方法可能产生该情况,claim可能导致bug,只用在测试
if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
{
// 插入StoreLoad内存屏障/栅栏,保证可见性。
// 因为publish使用的是set()/putOrderedLong,并不保证其他消费者能及时看见发布的数据
// 当我再次申请更多的空间时,必须保证消费者能消费发布的数据
cursor.setVolatile(nextValue); // StoreLoad fence
long minSequence;
// 如果末端的消费者们仍然没让出该插槽则等待,直到消费者们让出该插槽
// 注意:这是导致死锁的重要原因!
// 死锁分析:如果消费者挂掉了,而它的sequence没有从gatingSequences中删除的话,则生产者会死锁,它永远等不到消费者更新。
while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue)))
{
LockSupport.parkNanos(1L);
}
// 缓存生产者们最新的消费进度。
// (该值可能是大于wrapPoint的,那么如果下一次的 wrapPoint小于等于cachedValue则可以直接进行分配)
// 比如:我可能需要一个插槽位置,结果突然直接消费者们让出来3个插槽位置
this.cachedValue = minSequence;
}
// 这里只写了缓存,并未写volatile变量,因为只是预分配了空间但是并未被发布数据,不需要让其他消费者感知到。
// 消费者只会感知到真正被发布的序号
this.nextValue = nextSequence;
return nextSequence;
}
}
RingBuffer.next 方法就是申请空间用于发布数据。
- RingBuffer.publish方法
abstract class SingleProducerSequencerPad extends AbstractSequencer{
public void publish(long sequence)
{
// 更新发布进度,使用的是set(putOrderedLong),并没有保证对其他线程立即可见(最终会看见)
// 在下一次申请更多的空间时,如果发现需要消费者加快消费,则必须保证数据对消费者可见
cursor.set(sequence);
// 唤醒阻塞的消费者们(事件处理器们)
waitStrategy.signalAllWhenBlocking();
}
}
publish主要发布数据,对消费者可⻅,然后通知消费者消费。
等待策略
等待策略示例中使用的BlockingWaitStrategy,以这个策略为例:
public final class BlockingWaitStrategy implements WaitStrategy{
private final Lock lock = new ReentrantLock();
private final Condition processorNotifyCondition = lock.newCondition();
public long waitFor(long sequence, Sequence cursorSequence, Sequence dependentSequence, SequenceBarrier barrier)
throws AlertException, InterruptedException
{
// 确保生产者已生产者该数据,这期间可能阻塞
long availableSequence;
if (cursorSequence.get() < sequence)
{
lock.lock();
try
{
while (cursorSequence.get() < sequence)
{
barrier.checkAlert();
processorNotifyCondition.await();
}
}
finally
{
lock.unlock();
}
}
// 等待前驱消费者消费完对应的事件,这是实现消费者之间happens-before的关键
while ((availableSequence = dependentSequence.get()) < sequence)
{
barrier.checkAlert();
ThreadHints.onSpinWait();
}
return availableSequence;
}
}
BlockingWaitStrategy通过锁进行阻塞的策略,其他的就不一一介绍了。
优秀的设计
伪共享设计
伪共享问题: 在CPU中,为了提高 CPU 的速度,CPU有一个高速缓存 Cache。在 高速缓存中,读写数据的最小单位为缓存行(Cache Line),它是从主存(memory)复制到缓 存(Cache)的最小单位,一般为32 字节到128字节。
如果两个变量存放在一个缓存行中时,在多线程访问中,可能会相互影响彼此的性能。如 图 5.4 所示,假设X和丫在同一个缓存行。运行在 CPU1 上的线程更新了又,那么 CPU2 上的 缓存行就会失效,同一行的¥ 即使没有修改也会变成无效,导致 Cache 无法命中。接着,如果 在CPU2 上的线程更新了Y,则导致 CPU1 上的缓存行又失效(此时,同一行的又 又变得无法 访问)。这种情况反反复复发生,无疑是一个潜在的性能杀手。如果 CPU 经常不能命中缓存, 那么系统的吞吐量就会急剧下降。
为了使这种情况不发生,一种可行的做法就是在又变量的前后空间都先占据一定的位置(把 它叫做 padding 吧,用来填充用的)。这样,当内存被读入缓存中时,这个缓存行中,只有x 一个变量实际是有效的,因此就不会发生多个线程同时修改缓存行中不同变量而导致变量全体 失效的情况。
Disruptor 框架充分考虑了这个问题,它的核心组件 Sequence 会被非常频繁的访问(每次入 队,它都会被加1),其基本结构如下:
class LhsPadding {
protected long p1, p2, p3, p4, p5, p6, p7; // 填充CacheLine
}
class RhsPadding extends Value{
protected long p9, p10, p11, p12, p13, p14, p15; // 填充
CacheLine
}
虽然在 Sequence 中,主要使用的只有 value。但是,通过 LhsPadding 和 RhsPadding,在这 个 value 的前后安置了一些占位空间,使得 value 可以无冲突的存在于缓存中。
无锁设计
Disruptor 核心逻辑不用锁,在需要确保操作是线程安全的地方, Disruptor 使用了CAS(Compare And Swap/Set)操作,这是一个CPU级别的指 令,类似乐观锁。
RingBuffer设计
RingBuffer示例:
ringBuffer拥有一个序号,这个序号指向数组中下一个可用的元素。随着你不停地填充这 个buffer(可能也会有相应的读取),这个序号会一直增⻓,直到绕过这个环。 Disruptor 借助了 Sequence 和 RingBuffer 巧妙的实现了生产者和消费者的协作,同时也保证了高性 能。
另一方面, Disruptor 在使用 RingBuffer 时不会删除Buffer中的数据。从上面代码分析可 以看出,在创建 RingBuffer 时会填充Buffer的数据,然后整个Buffer中的对象不会再被删除 和添加。不需要花大量的时间用于垃圾回收。不像链表那样,需要为每一个添加到其上面的 对象创造节点对象—对应的,当删除节点时,需要执行相应的内存清理操作。
同时,由于 RingBuffer 是数组,数组内元素的内存地址是连续存储的,因此根据Cache Line共享特性,CPU无需时不时去主存加载数组中的下一个元素,提升 RingBuffer 访问性 能。