【源码笔记】专注于Java后端系列框架源码分析,Github地址:https://github.com/yuanmabiji/Java-SourceCode-Blogs
1 前言
Disruptor广播模式与执行顺序链源码分析 这篇文章中主要讲了Disruptor广播模式与执行链的构建原理,Disruptor广播模式跟MQ
的广播模式功能是一样的即生产者生产的消息会广播到每个消费者;不过相信大家在使用MQ
的过程中更多使用的是MQ
的协作者模式即同一个消费者组的消费者共同消费生产者生产的消息,同样,Disruptor
也一样提供了协作者模式,本篇文章将来探索Disruptor
消费者协作者模式源码,不过如果Disruptor广播模式与执行顺序链源码 搞懂的话,本篇文章并不算难。
2 协作模式DEMO
同样,先来运行一个DEMO
,后面再进入源码分析环节。
/**
* Disruptor消费者协作模式:类似于MQ的消费者组模式
*/
public class LongEventMain
{
private static final int BUFFER_SIZE = 1;
public static void main(String[] args) throws Exception
{
// 1 创建RingBuffer
RingBuffer<LongEvent> ringBuffer =
RingBuffer.create(ProducerType.SINGLE,
new LongEventFactory() {
public LongEvent newInstance() {
return new LongEvent();
}
},
BUFFER_SIZE,
new YieldingWaitStrategy());
//2 通过ringBuffer创建一个消费者与生产者之间的屏障,避免消费者消费速度赶上生产者
SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();
//3 创建多个消费者数组来协作消费生产者生产的数据,类似于mq的消费者组模式
WorkHandler[] workHandlers = new LongEventHandler[5];
for(int i = 0; i < workHandlers.length; i++) {
workHandlers[i] = new LongEventHandler("WorkHandler-" + i);
}
//4 构建多消费者协作模式工作池
WorkerPool<LongEvent> workerPool = new WorkerPool<LongEvent>(
ringBuffer,
sequenceBarrier,
new EventExceptionHandler(),
workHandlers);
//5 设置GatingSequences,创建一个生产者与消费者组之间的屏障,避免生产者生产速度赶上消费最慢的消费者
ringBuffer.addGatingSequences(workerPool.getWorkerSequences());
//6 启动多消费者协作模式工作池workerPool
workerPool.start(Executors.newFixedThreadPool(10));
// 7 生产者生产10条数据
for (int i = 0; i < 10; i++) {
long sequence = ringBuffer.next();
try {
LongEvent longEvent = ringBuffer.get(sequence);
longEvent.set(i);
} finally {
ringBuffer.publish(sequence);
}
}
}
static class LongEvent
{
private long value;
public void set(long value)
{
this.value = value;
}
public long get() {
return this.value;
}
}
static class LongEventFactory implements EventFactory<LongEvent>
{
@Override
public LongEvent newInstance()
{
return new LongEvent();
}
}
static class LongEventHandler implements WorkHandler<LongEvent>
{
private String name;
public LongEventHandler(String name) {
this.name = name;
}
@Override
public void onEvent(LongEvent event) throws Exception {
System.out.println(Thread.currentThread().getName() + ":" + this.name + " consumes " + event.value);
}
}
static class EventExceptionHandler implements ExceptionHandler<LongEvent> {
public void handleEventException(Throwable ex, long sequence, LongEvent event) {
}
public void handleOnStartException(Throwable ex) {
}
public void handleOnShutdownException(Throwable ex) {
}
}
}
运行结果:
3 Disruptor消费者协作模式源码分析
因为Disruptor
消费者协作模式源码跟Disruptor广播模式与执行顺序链源码 大同小异,这里主要分析下Disruptor
消费者协作模式的实现思想和分析下跟Disruptor广播模式与执行顺序链源码 的区别之处。
下面这张图中有1个生产者和2个消费者,2个消费者共同消费生产者生产的数据,消费者们消费速度不能超过生产者生产的速度,生产者也不能超过最慢的那个消费者的消费速度。
实现的核心思想如下:
首先构建消费者和生产者之间的
sequenceBarrier
屏障,sequenceBarrier
底层逻辑实质是当前消费者的消费sequence
跟生产者的生产sequence
做比较,保证消费者消费不能赶上生产者。源码如下:
// WorkProcessor.java public void run() { // 省略非关键源码... boolean processedSequence = true; long cachedAvailableSequence = Long.MIN_VALUE; long nextSequence = sequence.get(); T event = null; // 死循环 while (true) { try { // 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; // 【重要】这段逻辑是WorkProcessor和BatchEventProcessor消费逻辑的主要区别:当前消费者通过生产者workSequence的CAS操作来感知其他消费者消费进度,如果生产者workSequence被其他消费者消费了,那么就继续while循环,获取下一个生产者workSequence消费。这段代码也是多消费者协作模式轮流消费生产者数据的关键之处,因为nextSequence都是+1的,一个消费者获取到nextSequence后会进入业务处理逻辑,业务处理一般比较长时间,此时生产者的workSequence自然被下一个消费者消费,以此类推,保证了每个消费者轮流消费生产者数据的结果。 do { // 这两句不是原子操作 nextSequence = workSequence.get() + 1L; sequence.set(nextSequence - 1L); }// 多消费者协作消费数据,workSequence是全局变量,存在线程安全问题,用CAS操作 while (!workSequence.compareAndSet(nextSequence - 1L, nextSequence)); } // 如果消费者消费速度没有赶上生产者生产速度,那么进行消费 if (cachedAvailableSequence >= nextSequence) { event = ringBuffer.get(nextSequence); workHandler.onEvent(event); processedSequence = true; } // 消费者消费速度赶上生产者生产速度,那么根据WaitStrategy策略进行等待 else { cachedAvailableSequence = sequenceBarrier.waitFor(nextSequence); } } catch (final TimeoutException e) { // 省略非关键源码... } } // 省略非关键源码... }
这段逻辑是
WorkProcessor
和BatchEventProcessor
消费逻辑的主要区别:这个do while循环(CAS)的目的主要是通过多消费者线程争夺生产者sequence
即workSequence
的过程,当前消费者获取生产者workSequence
后,然后给当前sequence
设消费进度,但这两句不是原子操作,存在多线程问题, 但此时是多消费者并发消费,当前消费者获得生产者sequence
时,此时可能其他消费者也会获取同一个生产者sequence
,因此需要通过workSequence
的CAS操作来判断当前消费者获取的生产者sequence
有没有被其他消费者并发获取消费,如果有,那么继续循环获取下一个生产者workSequence
;没有就消费获取到的当前生产者workSequence
,从而保证了多消费者线程并发消费workSequence
的线程安全问题。其次构建生产者与消费者之间的
gatingSequence
屏障,gatingSequence
屏障底层逻辑就是拿生产者生产sequence
跟最慢的那个消费者的消费sequence
做比较,保证生产速度不能赶上最慢消费者。源码实现同Disruptor广播模式与执行顺序链源码 。
4 小结
目前Disruptor
源码分析文章就告一段落了,需要更详细源码注释请参见:https://github.com/yuanmabiji/disruptor 。目前开源中间件或框架应用Disruptor
框架的不多,目前了解到的有Canal
、Log4j2
和Jstorm
,百度uid-generator
也借鉴了Disruptor
的部分实现思路吧,另外,Disruptor
在领域驱动模型中也有应用。也就是说Disruptor
框架性能虽好,但其不流行,这又是说明原因呢?这个疑问暂且留到后面进行探讨。