Disruptor 是英国外汇交易公司 LMAX 开发的一个高性能队列。很多知名开源项目里,比如 canal 、log4j2、 storm 都是用了 Disruptor 以提升系统性能 。

这篇文章,我们通过两个例子一步一个脚印帮助同学们入门 Disruptor 。

1 环形缓冲区

下图展示了 Disruptor 的流程图 。

和线程池机制非常类似, Disruptor 也是非常典型的生产者/消费者模式。线程池存储提交任务的容器是阻塞队列,而 Disruptor 使用的是环形缓冲区 RingBuffer。

环形缓冲区的设计相比阻塞队列有如下优点:

  • 环形数组结构

为了避免垃圾回收,采用数组而非链表。同时,数组对处理器的缓存机制更加友好。

  • 元素位置定位

数组长度2^n,通过位运算,加快定位的速度。下标采取递增的形式,不用担心 index 溢出的问题。index 是 long 类型,即使100万QPS的处理速度,也需要30万年才能用完。

  • 无锁设计

每个生产者或者消费者线程,会先申请可以操作的元素在数组中的位置,申请到之后,直接在该位置写入或者读取数据。

2 写一个Hello world

我们写一个非常简单的例子:生产者传递一个单一的长整型值给消费者,而消费者将简单地打印出这个值。

2.1 添加依赖

<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.6</version>
</dependency>

2.2 定义事件

首先,我们将定义一个事件(Event),它将携带数据,并且在接下来的所有示例中都是通用的。

public class LongEvent {

    private long value;

    public void set(long value) {
this.value = value;
} @Override
public String toString() {
return "LongEvent{" + "value=" + value + '}';
}
}

为了让 Disruptor 为我们预分配这些事件,我们需要一个 EventFactory 来执行构造。这可以是一个方法引用,比如 LongEvent::new ,或者是 EventFactory 接口的显式实现:

public class LongEventFactory implements EventFactory<LongEvent> {
@Override
public LongEvent newInstance() {
return new LongEvent();
}
}

2.3 定义消费者

定义了事件,我们需要创建一个消费者来处理这些事件。我们会创建一个事件处理器(EventHandler),它会将把值打印到控制台上。

public class LongEventHandler implements EventHandler<LongEvent> {
@Override
public void onEvent(LongEvent longEvent, long sequence, boolean endOfBatch) throws Exception {
System.out.println("currentThread:" + Thread.currentThread().getName() + " Event: " + longEvent);
}
}

2.4 发布

public class LongEventMain {
public static void main(String[] args) throws Exception {
int bufferSize = 2;
Disruptor<LongEvent> disruptor =
new Disruptor<>(
new LongEventFactory(),
bufferSize,
DaemonThreadFactory.INSTANCE,
ProducerType.SINGLE,
new BlockingWaitStrategy());
disruptor.handleEventsWith(new LongEventHandler());
disruptor.start(); RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++) {
bb.putLong(0, l);
ringBuffer.publishEvent((event, sequence, buffer) -> event.set(buffer.getLong(0)), bb);
Thread.sleep(1000);
}
}
}

整个发布流程分为四个部分:

  1. 指定环形缓冲区的大小,必须是2的幂次方,例子中设置的值是 1024 ;

  2. 构建 Disruptor ,参数分别是事件工厂EventFactory 、环形缓冲区的大小ringBufferSize 、处理器线程池 、生产者类型(单生产者/多生产者)、消费者阻塞策略 ;

  3. 定义事件处理器eventHandler,我们这里的逻辑是打印数据打印在控制台;

  4. 启动 Disruptor,从 Disruptor 中获取环形缓冲区ringBuffer,在 for 循环里 ,调用环形队列的publishEvent方法。

    这里使用了 ByteBuffer 做为数据的存储容器 , 方便作为参数传递。

我们来看下执行结果 :

3 日志处理

3.1 应用场景

上面的例子比较简单,但假如要应用到生产环境,就显得非常粗糙。

我们模拟一个日志处理的场景,用户进入视频播放页面,浏览器定时的发送浏览日志到服务端,服务端将日志存储起来。

3.2 核心类设计

我们定义一个 DisruptorManager 管理器 , 管理器包含三个核心参数:消费者监听器 DataEventListener、消费者数量、环形队列长度。

public class DisruptorManager<T> {

    private static final Integer DEFAULT_CONSUMER_SIZE = 4;

    public static final Integer DEFAULT_SIZE = 4096 << 1 << 1;

    private DataEventListener<T> dataEventListener;

    private DisruptorProducer<T> producer;

    private int ringBufferSize;

    private int consumerSize;

    public DisruptorManager(DataEventListener<T> dataEventListener) {
this(dataEventListener, DEFAULT_CONSUMER_SIZE, DEFAULT_SIZE);
} public DisruptorManager(DataEventListener<T> dataEventListener, final int consumerSize, final int ringBufferSize) {
this.dataEventListener = dataEventListener;
this.ringBufferSize = ringBufferSize;
this.consumerSize = consumerSize;
} public void start() {
EventFactory<DataEvent<T>> eventFactory = new DisruptorEventFactory<>();
Disruptor<DataEvent<T>> disruptor = new Disruptor<>(
eventFactory,
ringBufferSize,
DisruptorThreadFactory.create("consumer-thread", false),
ProducerType.MULTI,
new BlockingWaitStrategy()
);
DisruptorConsumer<T>[] consumers = new DisruptorConsumer[consumerSize];
for (int i = 0; i < consumerSize; i++) {
consumers[i] = new DisruptorConsumer<>(dataEventListener);
}
disruptor.handleEventsWithWorkerPool(consumers);
disruptor.start();
RingBuffer<DataEvent<T>> ringBuffer = disruptor.getRingBuffer();
this.producer = new DisruptorProducer<>(ringBuffer, disruptor);
} public DisruptorProducer getProducer() {
return this.producer;
} }

首先和 Hello world 代码中的不同的点,Disruptor 的构造函数中我们自定义了消费者的处理器线程。

DisruptorThreadFactory.create("consumer-thread", false),

然后我们定义消费者的业务逻辑 :

DisruptorConsumer<T>[] consumers = new DisruptorConsumer[consumerSize];
for (int i = 0; i < consumerSize; i++) {
consumers[i] = new DisruptorConsumer<>(dataEventListener);
}
disruptor.handleEventsWithWorkerPool(consumers);

消费者本质上是workHandler的实现类,只不过初始化时将 DataEventListener 作为构造函数的参数。

public class DisruptorConsumer<T> implements WorkHandler<DataEvent<T>> {

    private DataEventListener<T> dataEventListener;

    public DisruptorConsumer(DataEventListener dataEventListener) {
this.dataEventListener = dataEventListener;
} @Override
public void onEvent(DataEvent<T> dataEvent) throws Exception {
if (dataEvent != null) {
dataEventListener.processDataEvent(dataEvent);
}
} }

因为我们是希望线程池并行的处理这些消息数据,使用的是disruptor.handleEventsWithWorkerPool 可以保证每个事件只会由一个工作处理器处理。

在 springboot 项目中,我们需要初始化相关 bean。

@Configuration
@AutoConfigureBefore(RedisConfig.class)
public class DisruptorConfig { private final static Logger logger = LoggerFactory.getLogger(DisruptorConfig.class); private final static String LIST_KEY = "disruptor:list"; @Autowired
private RedissonClient redissonClient; @Bean
public DataEventListener<String> createConsumerListener() {
DataEventListener<String> dataEventListener = new DataEventListener<String>() {
@Override
public void processDataEvent(DataEvent<String> dataEvent) throws InterruptedException {
logger.info("processDateEvent data:" + dataEvent.getData());
redissonClient.getList(LIST_KEY).add(dataEvent.getData());
}
};
return dataEventListener;
} @Bean
public DisruptorProducer<String> createProducer(DataEventListener dataEventListener) {
DisruptorManager disruptorManage = new DisruptorManager(dataEventListener,
8,
1024 * 1024);
disruptorManage.start();
return disruptorManage.getProducer();
}

首先,我们定义好消费者的事件监听器,然后定义 DisruptorProducer, 该类用来将数据提交到环形队列。

public class DisruptorProducer<T> {

    private final Logger logger = LoggerFactory.getLogger(DisruptorProducer.class);

    private final RingBuffer<DataEvent<T>> ringBuffer;

    private final Disruptor<DataEvent<T>> disruptor;

    private final EventTranslatorOneArg<DataEvent<T>, T> translatorOneArg = (event, sequence, t) -> event.setData(t);

    public DisruptorProducer(final RingBuffer<DataEvent<T>> ringBuffer, final Disruptor<DataEvent<T>> disruptor) {
this.ringBuffer = ringBuffer;
this.disruptor = disruptor;
} /**
* Send a data.
*
* @param data the data
*/
public void onData(final T data) {
try {
ringBuffer.publishEvent(translatorOneArg, data);
} catch (Exception ex) {
logger.error("publish event error:", ex);
}
} public void shutdown() {
if (null != disruptor) {
disruptor.shutdown();
}
} }

最后,在控制器中,接收前端请求:

@Autowired
private DisruptorProducer<String> producer; @GetMapping("/pushlog")
public ResponseEntity pushlog(String log) {
producer.onData(log);
return ResponseEntity.successResult(null);
}

从下图中,我们可以看到从控制器接收到请求后,消费处理器线程不断地将数据打印出来,并且发送到队列中。

4 写到最后

日志处理的例子里,我们试图封装 Disruptor 相关 API ,以便在 springboot 项目中更方便的使用。

笔者在测试过程时,发现若消费逻辑慢的时候,生产者发送数据事件时,可能会阻塞。

为什么生产者会阻塞,Disruptor 的核心原理是什么 ,如何使用 Disruptor 的高级特性顺序依赖执行 ?

正因为有这些疑问,笔者觉得深入理解 Disruptor 原理特别有必要,笔者也会在接下来的文章里一一为大家答疑解惑。


参考资料:

https://lmax-exchange.github.io/disruptor/disruptor.html

https://zhuanlan.zhihu.com/p/45575008

两个例子带你入门 Disruptor的更多相关文章

  1. 一个有趣的小例子,带你入门协程模块-asyncio

    一个有趣的小例子,带你入门协程模块-asyncio 上篇文章写了关于yield from的用法,简单的了解异步模式,[https://www.cnblogs.com/c-x-a/p/10106031. ...

  2. 一个少女心满满的例子带你入门canvas

    https://blog.csdn.net/sunshine940326/article/details/76572850 本文首发于我的个人博客:http://cherryblog.site/ gi ...

  3. 《Java从入门到失业》第四章:类和对象(4.3):一个完整的例子带你深入类和对象

    4.3一个完整的例子带你深入类和对象 到此为止,我们基本掌握了类和对象的基础知识,并且还学会了String类的基本使用,下面我想用一个实际的小例子,逐步来讨论类和对象的一些其他知识点. 4.3.1需求 ...

  4. 可能是史上最强大的js图表库——ECharts带你入门

    PS:之前的那篇博客Highcharts——让你的网页上图表画的飞起 ,评论中,花儿笑弯了腰 和 StanZhai 两位仁兄让我试试 ECharts ,去主页看到<Why ECharts ?&g ...

  5. 史上最强大的js图表库——ECharts带你入门(转)

    出处:http://www.cnblogs.com/zrtqsk/p/4019412.html PS:之前的那篇博客Highcharts——让你的网页上图表画的飞起 ,评论中,花儿笑弯了腰 和 Sta ...

  6. [转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程

    一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http ...

  7. C#单元测试,带你入门

    注:本文示例环境 VS2017 XUnit 2.2.0 单元测试框架 xunit.runner.visualstudio 2.2.0 测试运行工具 Moq 4.7.10 模拟框架 为什么要编写单元测试 ...

  8. SQLite 带你入门

    SQLite数据库相较于我们常用的Mysql,Oracle而言,实在是轻量得不行(最低只占几百K的内存).平时开发或生产环境中使用各种类型的数据库,可能都需要先安装数据库服务(server),然后才能 ...

  9. 一天带你入门到放弃vue.js(二)

    接下来我们继续学习一天带你入门到放弃系列vue.js(二),如有问题请留言讨论! v-if index.html <div id="app"> <p v-if=& ...

  10. 解决Linux终端乱码的两则例子

    现象描述 我们先来说一下出现乱码的原因. 例子 先举个实际的例子,我们一般通过ssh远程到服务器上进行操作.当在终端上执行一些有输出的任务时,有可能会遇到乱码,特别是输出中有中文时. 比如,我登陆上o ...

随机推荐

  1. .netcore中的虚拟文件EmbeddedFile

    以前一直比较好奇像swagger,cap,skywalking等组件是如何实现引用一个dll即可在网页上展示界面的,难道这么多html,js,css等都是硬编码写死在代码文件中的?后面接触apb里面也 ...

  2. Vue3从入门到精通(二)

    vue3 侦听器 在Vue3中,侦听器的使用方式与Vue2相同,可以使用watch选项或$watch方法来创建侦听器.不同之处在于,Vue3中取消了immediate选项,同时提供了新的选项和API. ...

  3. 【Python入门教程】Python常用表格函数&操作(xlrd、xlwt、openpyxl、xlwings)

    ​         在我们使用Python时,避免不了与Excel打交道.同样Python的三方库和代码的简洁性也为我们处理大数据提供了便利.今天给大家介绍一下常用的处理表格的函数,同时还有一些常用的 ...

  4. nordic——NCS下的DFU升级(基于NCS)

    一.简介 在NCS中有多种的DFU选择,强烈推荐使用MCUboot,当然如果你需要选择传统的nrf_DFU也是可以的,但是要用到官方修改的源文件. 关于mcuboot,原理性的东西在官网和官方博客中有 ...

  5. 如何使用 Terraform 和 Git 分支有效管理多环境?

    作者|Sumeet Ninawe 翻译|Seal软件 链接|https://spacelift.io/blog/terraform-environments 通常我们使用 Terraform 将我们的 ...

  6. Batch Normalization及其反向传播及bn层的作用

    笔记: Batch Normalization及其反向传播 重点: 在神经网络中,网络是分层的,可以把每一层视为一个单独的分类器,将一个网络看成分类器的串联.这就意味着,在训练过程中,随着某一层分类器 ...

  7. 【小小demo】Springboot + Vue 增删改查

    vue-table-ui 该工程提供的是 一个简单的 Vue + Element-UI 的表格,增删改查操作. 工程代码在最下面. 环境 jdk1.8 idea maven springboot 2. ...

  8. 【Nginx】Nginx访问静态资源

    Nginx访问静态资源 即通过IP:端口/文件名 访问文件实现. 修改Nginx配置 location / { # root html; # index index.html index.htm; a ...

  9. hexo博客yilia主题_缺失模块_解决方案

    hexo博客yilia主题,左侧栏目有一个全部文章的按钮,刚开始开始报错缺失模块,如下图: 我解决了这个问题着实不容易饶了弯路,但是跟着提示步骤,其实很简单,走起: 1.查看node版本 win键+R ...

  10. 快速上手 | Datavines 两表值比对规则用法

    Datavines 是一站式开源数据可观测性平台,提供元数据管理.数据概览报告.数据质量管理,数据分布查询.数据趋势洞察等核心能力,致力于帮助用户全面地了解和掌管数据,让您做到心中有数. 场景 比较某 ...