
架构师入门笔记七 并发框架Disruptor快速入门
1. 什么是Disruptor
Disruptor它是一个高性能的异步处理的开源并发框架,能够在无锁的情况下实现网络的Queue并发操作。可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式的实现,或者事件监听模式的实现。
2. HelloWorld代码
在生产者-消费者设计模型的中,采用有界队列BlockingQueue作为存储任务的容器,生产者将任务分配给容器,再由容器分配给消费者。Disruptor框架和该模型很相似。区别在于它用一个环形的RingBuffer作为存储任务的容器。消费者是主动从RingBuffer中获取数据而不是等待分配。从某种角度来说,Disruptor框架是升级版的生产者-消费者模型。
这里用一段代码学习 Disruptor框架,业务逻辑是把10000以内的数据全部打印出来
1) 首先是创建传递数据的Event(Event是从生产者到消费者过程中所处理的数据单元)类,该类是由用户定义。
/**
* 第一步:创建一个数据单元Event
* Event:从生产者到消费者过程中所处理的数据单元
*
*/
public class MyDataEvent {
private Long value;
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
}
2)创建一个实例化Event的工厂类
import com.lmax.disruptor.EventFactory;
/**
* 第二步创建工厂类实例化Event
* EventFactory 工厂,用于实例化Event类
*/
public class MyDataEventFactory implements EventFactory<MyDataEvent>{
@Override
public MyDataEvent newInstance() {
return new MyDataEvent();
}
}
3)创建一个事件处理器,也就是消费者,这里只做数据打印的事件。
import com.lmax.disruptor.EventHandler;
/**
* 第三步:消费端
* EventHandler:消费者,也可以理解为事件处理器
*/
public class MyDataEventHandler implements EventHandler<MyDataEvent>{
@Override
public void onEvent(MyDataEvent myDataEvent, long arg1, boolean arg2)
throws Exception {
// 处理事件 ....
System.out.println("处理事件,打印数据: " + myDataEvent.getValue());
}
}
4)生产者发布事件
import com.lmax.disruptor.RingBuffer;
/**
* 第四步:生产端
* 生产者
*/
public class MyDataEventProducer {
private final RingBuffer<MyDataEvent> ringBuffer; // 敲黑板! 很重要的知识点
public MyDataEventProducer(RingBuffer<MyDataEvent> ringBuffer) {
this.ringBuffer = ringBuffer;
}
/**
* 发布事件,每调用一次就发布一次事件
* 它的参数会通过事件传递给消费者
* @param byteBuffer 用 byteBuffer传参 是考虑到 Disruptor 是消息框架,而ByteBuffer又是读取时信道 (SocketChannel)最常用的缓冲区
*/
public void publishData(ByteBuffer byteBuffer){
// RingBuffer 是一个圆环,.next() 方法是获取下一个索引值
long sequence = ringBuffer.next();
try {
// 通过索引值获取其对象
MyDataEvent myDataEvent = ringBuffer.get(sequence);
// 给数据单元赋值
myDataEvent.setValue(byteBuffer.getLong(0)); // byteBuffer 的一个方法,文章中有链接
} catch (Exception e) {
e.printStackTrace();
} finally {
// 发布事件,其实就是发布索引 ,发布方法必须放在finally 中,避免出现阻塞情况。
ringBuffer.publish(sequence);
}
}
}
注意:
发布事件是两个步骤,第一步:先要从RingBuffer获取下一个事件槽(可以理解为索引),第二步再是发送事件。需要注意的是:获取的事件槽,就要发布该事件槽对应的事件。不然会出现混乱的情况。所以发布事件的代码要放在finally中。 java8的写法,文章底部有链接。
5)执行的Main方法,打印10000以内的数据
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
public class MyDataEventMain {
public static void main(String[] args) {
// step1 : 创建缓冲池
ExecutorService executor = Executors.newCachedThreadPool();
// step2 : 创建工厂
MyDataEventFactory factory = new MyDataEventFactory();
// step3 : 创建bufferSize ,也就是RingBuffer大小,必须是2的N次方
int ringBufferSize = 1024 * 1024;
// step4 : 创建disruptor
Disruptor<MyDataEvent> disruptor = new Disruptor<MyDataEvent>(factory, ringBufferSize, executor);
// step5 : 连接消费事件方法<消费者>
disruptor.handleEventsWith(new MyDataEventHandler());
// step6 : 启动
disruptor.start();
RingBuffer<MyDataEvent> ringBuffer = disruptor.getRingBuffer(); // 获取 ringBuffer
// step7 : 生产者发布事件
MyDataEventProducer producer = new MyDataEventProducer(ringBuffer);
ByteBuffer byteBuffer = ByteBuffer.allocate(128); // 创建一个容量为128字节的ByteBuffer
for (long data = 1; data <= 10000 ; data++) { // 不管是打印100,1000,10000,基本上都是一秒内输出。
byteBuffer.putLong(0, data); // 在下标为零的位置存储值
producer.publishData(byteBuffer); //
}
disruptor.shutdown(); // 关闭 disruptor,方法会堵塞,直至所有的事件都得到处理;
executor.shutdown(); // 关闭 disruptor 使用的线程池;如果需要的话,必须手动关闭, disruptor 在 shutdown 时不会自动关闭;
}
}
3 组件说明
从生产者-消费者的整体:
RingBuffer:环形队列,是Disruptor最为重要的组件,其作用是存储和更新Disruptor中流通的数据。
Sequence:递增序号(AtomicLong),Disruptor使用Sequence标识一个特殊组件处理的序号。每个重要的组件基本都有一个Sequence。
Producer:生产者,泛指通过Disruptor发布事件的用户代码(实际业务代码,而并发框架代码)生成Event数据。
Event:事件,从生产者到消费者过程中的数据单元。由用户定义代码。
EventHandler:消费者,代表Disruptor框架中的一个消费者接口,由用户实现代码,负责处理Event数据,进度通过Sequence控制。
(打个比方:餐饮店买奶茶
你去餐饮店买奶茶,先要去柜台找服务员点一杯红豆抹茶,服务员会给你一个55号的排队号,等到服务员大喊:‘55号,55号’,于是你就屁颠屁颠的去拿红豆抹茶;
“你去买红豆抹茶” 就是 Producer
“红豆抹茶” 就是 Event
“柜台” 就是 RingBuffer
“55号” 就是 Sequence
“你去拿红豆抹茶” 就是 EventHandler)
从Disruptor框架如何处理Event的细节:
Sequecer:Disruptor框架真正的核心,在生产者和消费者直接进行高效准确快速的数据传输。通过复杂的算法去协调生存者和消费者之间的关系。
SequenceBarrier:Sequecer具体的实施者,字面理解是序号屏障,其目的是决定消费者 消费Evnet的逻辑。(生产者发布事件快于消费,生产者等待。消费速度大于生产者发布事件速度,消费者监听)
EventProcessor:可以理解为具体的消费线程,最后把结果返回给EventHandler。
WaitStrategy:当消费者等待在SequenceBarrier上时,有许多可选的等待策略
- BusySpinWaitStrategy : 自旋等待,类似Linux Kernel使用的自旋锁。低延迟但同时对CPU资源的占用也多。
- BlockingWaitStrategy : 使用锁和条件变量。CPU资源的占用少,延迟大。
- SleepingWaitStrategy : 在多次循环尝试不成功后,选择让出CPU,等待下次调度,多次调度后仍不成功,尝试前睡眠一个纳秒级别的时间再尝试。这种策略平衡了延迟和CPU资源占用,但延迟不均匀。
- YieldingWaitStrategy : 在多次循环尝试不成功后,选择让出CPU,等待下次调。平衡了延迟和CPU资源占用,但延迟也比较均匀。
- PhasedBackoffWaitStrategy : 上面多种策略的综合,CPU资源的占用少,延迟大。
(打个比方:
柜台的服务员通知某位厨师:“55号要一杯红豆抹茶”,然后厨师准备拿机器做奶茶,发现机器都在使用中,于是厨师就盯着机器看,当有空闲的机器就立马占用,做好后就端给客户。如果等了很久都没有空闲的机器,厨师会跟客服员说:“55号的红豆抹茶,可能要多等一会”,然后工作人员就和客户协调一下说明情况。
“服务员” 就是 Sequecer
“某位厨师” 就是 SequenceBarrier
“用机器做红豆抹茶” 就是 EventProcessor
“发现没有空闲机器,厨师监听” 就是 WaitStrategy
)
打的比方可能不是很形象。如果不理解的,可以反复的敲打代码,多问问为什么这样写,这样做有什么好处。慢慢的就理解了。
4 优质博客
以上边上Disruptor基础知识和相关的代码笔记,方便自己查阅,同时也希望能帮助到读者。下一章Disruptor场景应用。更多干货尽在
ITDragon博客