一、基本介绍
1,什么是 Disruptor?
(1)Disruptor 是英国外汇交易公司 LMAX 开发的一个高性能的并发框架。可以认为是线程间通信的高效低延时的内存消息组件,它最大的特点是高性能。与 Kafka、RabbitMQ 用于服务间的消息队列不同,disruptor 一般用于一个 JVM 中多个线程间消息的传递。
(2)从功能上来看,Disruptor 实现了“队列”的功能,而且是一个有界队列(事实上它是一个无锁的线程间通信库)。作用与 ArrayBlockingQueue 有相似之处,但是 disruptor 从功能、性能上又都远好于 ArrayBlockingQueue。
2,Disruptor 的优势
(1)Disruptor 最直接的应用场景自然就是“生产者-消费者”模型的应用场合了,虽然这些我们使用 JDK 的 BlockingQueue 也能做到,但 Disruptor 的性能比 BlockingQueue 提高了 5~10 倍左右:
(2)也就是说 BlockingQueue 能做的,Disruptor 都能做到且做的更好。同时 Disruptor 还能做得更多:
- 同一个“事件”可以有多个消费者,消费者之间既可以并行处理,也可以相互依赖形成处理的先后次序(形成一个依赖图);
- 预分配用于存储事件内容的内存空间;
- 针对极高的性能目标而实现的极度优化和无锁的设计;
3,Disruptor 性能高效的原因
(1)环形数组结构
- 为了避免垃圾回收,Disruptor 的 RingBuffer 采用数组而非链表。同时,数组对处理器的缓存机制更加友好。
(2)快速的元素位置定位策略
- 由于 RingBuffer 是环状队列结构,在初始化时需要指定大小(即这个环最多可以容纳多少槽)。而 Disruptor 规定了 RingBuffer 大小必须是 2 的 n 次方,这样通过位运算,可以加快定位的速度。
- 同时下标采取递增的形式。不用担心 index 溢出的问题。index 是 long 类型,即使 100 万 QPS 的处理速度,也需要 30 万年才能用完。
(3)无锁设计
- 在需要确保操作是线程安全的地方,Disruptor 并不使用用锁,而是使用 CAS(Compare And Swap/Set)操作。每个生产者或者消费者线程,会先申请可以操作的元素在数组中的位置,申请到之后,直接在该位置写入或者读取数据。
二、Disruptor核心架构组件
- Ring Buffer:Ring Buffer在3.0版本以前被认为是Disruptor的核心组件,但是在之后的版本中只是负责存储和更新数据。在一些高级使用案例中用户也能进行自定义
- Sequence:Disruptor使用一组Sequence来作为一个手段来标识特定的组件的处理进度( RingBuffer/Consumer )。每个消费者和Disruptor本身都会维护一个Sequence。虽然一个 AtomicLong 也可以用于标识进度,但定义 Sequence 来负责该问题还有另一个目的,那就是防止不同的 Sequence 之间的CPU缓存伪共享(Flase Sharing)问题。
- Sequencer:Sequencer是Disruptor的真正核心。此接口有两个实现类 SingleProducerSequencer、MultiProducerSequencer ,它们定义在生产者和消费者之间快速、正确地传递数据的并发算法。
- Sequence Barrier:保持Sequencer和Consumer依赖的其它Consumer的 Sequence 的引用。除此之外还定义了决定 Consumer 是否还有可处理的事件的逻辑。
- Wait Strategy:Wait Strategy决定了一个消费者怎么等待生产者将事件(Event)放入Disruptor中。
- Event:从生产者到消费者传递的数据叫做Event。它不是一个被 Disruptor 定义的特定类型,而是由 Disruptor 的使用者定义并指定。
- EventProcessor:持有特定的消费者的Sequence,并且拥有一个主事件循环(main event loop)用于处理Disruptor的事件。其中BatchEventProcessor是其具体实现,实现了事件循环(event loop),并且会回调到实现了EventHandler的已使用过的实例中。
- EventHandler:由用户实现的接口,用于处理事件,是 Consumer 的真正实现
- Producer:生产者,只是泛指调用 Disruptor 发布事件的用户代码,Disruptor 没有定义特定接口或类型。
三、示例
1、pom引包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhouzy.drools</groupId>
<artifactId>zzyDrools</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zzyDrools</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- drools 规则引擎 版本 -->
<log4j2.version>2.5</log4j2.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<!-- 依赖项定义 -->
<dependencies>
<!-- Spring boot start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.30</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、消息体
package com.zhouzy.disruptor.model;
public class MessageVo {
private String code;
private String value;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
3、处理消息的handler
package com.zhouzy.disruptor.business;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.lmax.disruptor.EventHandler;
import com.zhouzy.disruptor.model.MessageVo;
public class HelloEventHandler implements EventHandler<MessageVo> {
Logger log = LoggerFactory.getLogger(HelloEventHandler.class);
@Override
public void onEvent(MessageVo event, long sequence, boolean endOfBatch) {
try {
//这里停止1000ms是为了确定消费消息是异步的
Thread.sleep(1000);
log.info("消费者处理消息开始");
if (event != null) {
log.info("消费者消费的信息是:{}",event);
}
} catch (Exception e) {
log.info("消费者处理消息失败");
}
log.info("消费者处理消息结束");
}
}
4、构造MQManage
package com.zhouzy.disruptor.business;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.zhouzy.disruptor.model.MessageVo;
@Configuration
public class MQManager {
@SuppressWarnings({ "deprecation", "unchecked" })
@Bean("MessageVo")
public RingBuffer<MessageVo> MessageVoRingBuffer() {
//定义用于事件处理的线程池, Disruptor通过java.util.concurrent.ExecutorSerivce提供的线程来触发consumer的事件处理
ExecutorService executor = Executors.newFixedThreadPool(2);
//指定事件工厂
HelloEventFactory factory = new HelloEventFactory();
//指定ringbuffer字节大小,必须为2的N次方(能将求模运算转为位运算提高效率),否则将影响效率
int bufferSize = 1024 * 256;
//单线程模式,获取额外的性能
Disruptor<MessageVo> disruptor = new Disruptor<>(factory, bufferSize, executor,
ProducerType.SINGLE, new BlockingWaitStrategy());
//设置事件业务处理器---消费者
disruptor.handleEventsWith(new HelloEventHandler());
// 启动disruptor线程
disruptor.start();
//获取ringbuffer环,用于接取生产者生产的事件
RingBuffer<MessageVo> ringBuffer = disruptor.getRingBuffer();
return ringBuffer;
}
}
5、生产者
public interface DisruptorMqService {
/**
* 消息
* @param message
*/
void sayHelloMq(String message);
}
@Component
@Service
public class DisruptorMqServiceImpl implements DisruptorMqService {
Logger log = LoggerFactory.getLogger(DisruptorMqServiceImpl.class);
@Autowired
private RingBuffer<MessageVo> MessageVoRingBuffer;
@Override
public void sayHelloMq(String message) {
log.info("record the message: {}",message);
//获取下一个Event槽的下标
long sequence = MessageVoRingBuffer.next();
try {
//给Event填充数据
MessageVo event = MessageVoRingBuffer.get(sequence);
event.setValue(message);
log.info("往消息队列中添加消息:{}", event);
} catch (Exception e) {
log.error("failed to add event to MessageVoRingBuffer for : e = {},{}",e,e.getMessage());
} finally {
//发布Event,激活观察者去消费,将sequence传递给改消费者
//注意最后的publish方法必须放在finally中以确保必须得到调用;如果某个请求的sequence未被提交将会堵塞后续的发布操作或者其他的producer
MessageVoRingBuffer.publish(sequence);
}
}
}
6、单元测试
package com.zhouzy.disruptor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.zhouzy.disruptor.business.DisruptorMqService;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DisruptorApplication.class)
public class DemoApplicationTests {
Logger log = LoggerFactory.getLogger(DemoApplicationTests.class);
@Autowired
private DisruptorMqService disruptorMqService;
/**
* 项目内部使用Disruptor做消息队列
* @throws Exception
*/
@Test
public void sayHelloMqTest() throws Exception{
disruptorMqService.sayHelloMq("消息到了,Hello world!");
log.info("消息队列已发送完毕");
//这里停止2000ms是为了确定是处理消息是异步的
Thread.sleep(2000);
}
}