文章首发于:clawhub.club
在定位项目中内存泄漏问题时,发现RingBuffer占用内存过大,这个是在使用log4j2时引入的jar,以前只知道必须使用这个Disruptor才可以用异步日志,
但是并不清楚Disruptor的一些实现,也没有通过编码的方式使用过,这次正好研究一下。
Disruptor是一个低延迟(low-latency),高吞吐量(high-throughput)的事件发布订阅框架,用于一个JVM中多个线程之间的消息队列,
作用与ArrayBlockingQueue有相似之处,但是disruptor从功能、性能都远好于ArrayBlockingQueue。
相关概念
RingBuffer
应用需要传递的消息在Disruptor中称为Event(事件)。
RingBuffer是Event的数组,实现了阻塞队列的语义:
如果RingBuffer满了,则生产者会阻塞等待。
如果RingBuffer空了,则消费者会阻塞等待。
Sequence
在上文中,我提到“每个消费者需要自己维护一个指针”。这里的指针就是一个单调递增长整数(及其基于CAS的加法、获取操作),称为Sequence。
除了每个消费者需要维护一个指针外,RingBuffer自身也要维护一个全局指针(如上一节第2点所提到的),记录最后一条可以被消费的消息。
高性能的体现
无锁,无锁就没有锁竞争。当生产者、消费者线程数很高时,意义重大。所以,
往大里说,每个消费者维护自己的Sequence,基本没有跨线程共享的状态。
往小里说,Sequence的加法是CAS实现的。
当生产者需要判断RingBuffer是否已满时,用CAS比较原先RingBuffer的Event个数,和假定放入新Event后Event的个数。
如果CAS返回false,说明在判断期间,别的生产者加入了新Event;或者别的消费者拿走了Event。那么当前判断无效,需要重新判断。对象的复用,JVM运行时,一怕创建大对象,二怕创建很多小对象。这都会导致JVM堆碎片化、对象元数据存储的额外开销大。这是高性能Java应用的噩梦。
为了解决第二点“很多小对象”,主流开源框架都会自己维护、复用对象池。LMAX Disruptor也不例外。
生产者不是创建新的Event对象,放入到RingBuffer中。而是从RingBuffer中取出一个已有的Event对象,更新它所指向的业务数据,来代表一个逻辑上的新Event。
简单使用
pom
com.lmax
disruptor
3.4.2
LogEvent
自定义实体对象,充当“生产者-消费者”模型中的数据。
import java.util.Date;
/**
* 自定义实体对象,充当“生产者-消费者”模型中的数据
*/
public class LogEvent {
/**
* The Log id.
*/
private long logId;
/**
* The Content.
*/
private String content;
/**
* The Date.
*/
private Date date;
/**
* Gets log id.
*
* @return the log id
*/
public long getLogId() {
return logId;
}
/**
* Sets log id.
*
* @param logId the log id
*/
public void setLogId(long logId) {
this.logId = logId;
}
/**
* Gets content.
*
* @return the content
*/
public String getContent() {
return content;
}
/**
* Sets content.
*
* @param content the content
*/
public void setContent(String content) {
this.content = content;
}
/**
* Gets date.
*
* @return the date
*/
public Date getDate() {
return date;
}
/**
* Sets date.
*
* @param date the date
*/
public void setDate(Date date) {
this.date = date;
}
@Override
public String toString() {
return "logId=" + logId +
", content='" + content + '\'' +
", date=" + date;
}
}
LogEventFactory
实现EventFactory的接口,用于生产数据。
import com.lmax.disruptor.EventFactory;
/**
* 事件生成工厂,用来初始化预分配事件对象,即根据RingBuffer大小创建的实体对象
*/
public class LogEventFactory implements EventFactory {
@Override
public LogEvent newInstance() {
return new LogEvent();
}
}
LogEventProducer
自定义生产者。
import java.util.Date;
import com.lmax.disruptor.RingBuffer;
/**
* 自定义生产者
*/
public class LogEventProducer {
private RingBuffer ringBuffer;
public LogEventProducer(RingBuffer ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void onData(long logId, String content, Date date) {
//递增并返回循环缓冲区的下一个序列。
long seq = ringBuffer.next();
//获取RingBuffer中给定序列的事件
LogEvent logEvent = ringBuffer.get(seq);
logEvent.setLogId(logId);
logEvent.setContent(content);
logEvent.setDate(date);
//发布指定的序列。此操作将此特定消息标记为可读取。
ringBuffer.publish(seq);
}
}
LogEventProducerWithTranslator
将数据存储到自定义对象中并发布,通过在自定义类中新建EventTranslator类实现。
import java.util.Date;
import com.lmax.disruptor.EventTranslatorVararg;
import com.lmax.disruptor.RingBuffer;
/**
* 使用translator方式到事件生产者发布事件,通常使用该方法
*/
public class LogEventProducerWithTranslator {
/**
* 实现将另一个数据表示转换为从{@link RingBuffer}声明的事件。
*/
private EventTranslatorVararg eventTranslatorVararg = (EventTranslatorVararg) (event, sequence, args) -> {
event.setLogId((Long) args[0]);
event.setContent((String) args[1]);
event.setDate((Date) args[2]);
};
private RingBuffer ringBuffer;
public LogEventProducerWithTranslator(RingBuffer ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void onData(long logId, String content, Date date) {
//允许用户提供可变数量的参数
ringBuffer.publishEvent(eventTranslatorVararg, logId, content, date);
}
}
LogEventConsumer
自定义消费者。
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.RingBuffer;
/**
* 自定义消费者
*/
public class LogEventConsumer implements EventHandler {
/**
* The Name.
*/
private String name;
/**
* Instantiates a new Log event consumer.
*
* @param name the name
*/
public LogEventConsumer(String name) {
this.name = name;
}
/**
* 当发布者将事件发布到RingBuffer时调用。
*
* @param event published to the {@link RingBuffer}
* @param sequence 正在处理的事件的sequence
* @param endOfBatch 标志,指示这是否是来自{@link RingBuffer}的批处理中的最后一个事件
*/
@Override
public void onEvent(LogEvent event, long sequence, boolean endOfBatch) {
System.out.println("LogEventConsumer name:" + name + ",sequence:" + sequence + ",endOfBatch:" + endOfBatch + ",logEvent:" + event.toString());
}
}
LogEventMain
启动项,通过单一生产者,多生产者,单一消费者,多消费者的组合,测试了disruptor的功能。
import java.util.Date;
import java.util.concurrent.Executors;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.EventHandlerGroup;
import com.lmax.disruptor.dsl.ProducerType;
/**
* 启动
*/
public class LogEventMain {
/**
* The entry point of application.
*
* @param args the input arguments
* @throws InterruptedException the interrupted exception
*/
public static void main(String[] args) throws InterruptedException {
// 单个生产者和消费者的模式
producer();
// 使用EventTranslatorVararg的单个生产者和消费者模式
producerWithTranslator();
// 一个生产者,3个消费者,其中前面2个消费者完成后第3个消费者才可以消费
multiConsumer();
// 一个生产者,多个消费者,有2条支线
multiConsumers();
// 多个生产者,多个消费者,有2条消费者支线
multiProcedureConsumers();
}
/**
* 多个生产者,多个消费者,有2条消费者支线,其中消费者1和消费者3在同一条支线上,
* 消费者2和消费者4在同一条支线上,消费者5是消费者3和消费者4的终点消费者
* 这样的消费将会在消费者1和消费者2把所有的RingBuffer大小消费完成后才会执行消费者3和消费者4
* 在消费者3和消费者4把RingBuffer大小消费完成后才会执行消费者5
* 消费者5消费完RingBuffer大小后又按照上面的顺序来消费
* 如果剩余的生产数据比RingBuffer小,那么还是要依照顺序来
* 生产者只是多生产了数据
*/
public static void multiProcedureConsumers() throws InterruptedException {
LogEventFactory logEventFactory = new LogEventFactory();
//用于生成RingBuffer大小,其大小必须是2的n次方
int ringBufferSize = 2 << 3;
//定义Disruptor初始化信息
Disruptor disruptor = new Disruptor<>(logEventFactory, ringBufferSize, Executors.defaultThreadFactory(), ProducerType.MULTI, new YieldingWaitStrategy());
LogEventConsumer consumer1 = new LogEventConsumer("1");
LogEventConsumer consumer2 = new LogEventConsumer("2");
LogEventConsumer consumer3 = new LogEventConsumer("3");
LogEventConsumer consumer4 = new LogEventConsumer("4");
LogEventConsumer consumer5 = new LogEventConsumer("5");
//同时执行消费者1和消费者2
disruptor.handleEventsWith(consumer1, consumer2);
//消费者1后面执行消费者3
disruptor.after(consumer1).handleEventsWith(consumer3);
//消费者后面执行消费者4
disruptor.after(consumer2).handleEventsWith(consumer4);
//消费者3和消费者3执行完后执行消费者5
disruptor.after(consumer3, consumer4).handleEventsWith(consumer5);
//定义事件的开始
disruptor.start();
RingBuffer ringBuffer = disruptor.getRingBuffer();
//进行事件的发布
LogEventProducer logEventProducer = new LogEventProducer(ringBuffer);
LogEventProducer logEventProducer2 = new LogEventProducer(ringBuffer);
LogEventProducer logEventProducer3 = new LogEventProducer(ringBuffer);
for (int i = 0; i < 10; i++) {
logEventProducer.onData(i, "1-logEventProducer" + i, new Date());
logEventProducer2.onData(i, "2-logEventProducer" + i, new Date());
logEventProducer3.onData(i, "3-logEventProducer" + i, new Date());
}
Thread.sleep(1000);
//关闭Disruptor
disruptor.shutdown();
}
/**
* 一个生产者,多个消费者,有2条支线,其中消费者1和消费者3在同一条支线上,
* 消费者2和消费者4在同一条支线上,消费者5是消费者3和消费者4的终点消费者
* 这样的消费将会在消费者1和消费者2把所有的RingBuffer大小消费完成后才会执行消费者3和消费者4
* 在消费者3和消费者4把RingBuffer大小消费完成后才会执行消费者5
* 消费者5消费完RingBuffer大小后又按照上面的顺序来消费
* 如果剩余的生产数据比RingBuffer小,那么还是要依照顺序来
*/
public static void multiConsumers() throws InterruptedException {
LogEventFactory logEventFactory = new LogEventFactory();
//用于生成RingBuffer大小,其大小必须是2的n次方
int ringBufferSize = 2 << 3;
//定义Disruptor初始化信息
Disruptor disruptor = new Disruptor<>(logEventFactory, ringBufferSize, Executors.defaultThreadFactory(), ProducerType.SINGLE, new YieldingWaitStrategy());
LogEventConsumer consumer1 = new LogEventConsumer("1");
LogEventConsumer consumer2 = new LogEventConsumer("2");
LogEventConsumer consumer3 = new LogEventConsumer("3");
LogEventConsumer consumer4 = new LogEventConsumer("4");
LogEventConsumer consumer5 = new LogEventConsumer("5");
//同时执行消费者1和消费者2
disruptor.handleEventsWith(consumer1, consumer2);
//消费者1后面执行消费者3
disruptor.after(consumer1).handleEventsWith(consumer3);
//消费者后面执行消费者4
disruptor.after(consumer2).handleEventsWith(consumer4);
//消费者3和消费者3执行完后执行消费者5
disruptor.after(consumer3, consumer4).handleEventsWith(consumer5);
//定义事件的开始
disruptor.start();
RingBuffer ringBuffer = disruptor.getRingBuffer();
//进行事件的发布
LogEventProducer logEventProducer = new LogEventProducer(ringBuffer);
for (int i = 0; i < 10; i++) {
logEventProducer.onData(i, "logEventProducer" + i, new Date());
}
Thread.sleep(1000);
//关闭Disruptor
disruptor.shutdown();
}
/**
* 一个生产者,3个消费者,其中前面2个消费者完成后第3个消费者才可以消费
* 也即使说当前面2个消费者把所有的RingBuffer占领完成,同时都消费完成后才会有第3个消费者的消费
* 当发布的事件数量大于RingBuffer的大小的时候,在第3个消费者消费完RingBuffer大小的时候前面2个消费者才能继续消费,序号递增的
*/
public static void multiConsumer() throws InterruptedException {
LogEventFactory logEventFactory = new LogEventFactory();
//用于生成RingBuffer大小,其大小必须是2的n次方
int ringBufferSize = 2 << 3;
//定义Disruptor初始化信息
Disruptor disruptor = new Disruptor<>(logEventFactory, ringBufferSize, Executors.defaultThreadFactory(), ProducerType.SINGLE, new YieldingWaitStrategy());
//设置多个消费者
EventHandlerGroup eventEventHandlerGroup = disruptor.handleEventsWith(new LogEventConsumer("1"), new LogEventConsumer("2"));
eventEventHandlerGroup.then(new LogEventConsumer("3"));
//启动事件的开始
disruptor.start();
RingBuffer ringBuffer = disruptor.getRingBuffer();
//进行事件的发布
LogEventProducerWithTranslator producerWithTranslator = new LogEventProducerWithTranslator(ringBuffer);
for (int i = 0; i < 10; i++) {
producerWithTranslator.onData(i, "producerWithTranslator" + i, new Date());
}
Thread.sleep(1000);
//关闭Disruptor
disruptor.shutdown();
}
/**
* 使用EventTranslatorVararg的单个生产者和消费者模式
*/
public static void producerWithTranslator() throws InterruptedException {
LogEventFactory logEventFactory = new LogEventFactory();
//用于生成RingBuffer大小,其大小必须是2的n次方
int ringBufferSize = 2 << 3;
//定义Disruptor初始化信息
Disruptor disruptor = new Disruptor<>(logEventFactory, ringBufferSize, Executors.defaultThreadFactory(), ProducerType.SINGLE, new YieldingWaitStrategy());
//定义处理事件的消费者
disruptor.handleEventsWith(new LogEventConsumer("1"));
//定义事件的开始
disruptor.start();
RingBuffer ringBuffer = disruptor.getRingBuffer();
//进行事件的发布
LogEventProducerWithTranslator producerWithTranslator = new LogEventProducerWithTranslator(ringBuffer);
for (int i = 0; i < 10; i++) {
producerWithTranslator.onData(i, "producerWithTranslator" + i, new Date());
}
Thread.sleep(1000);
//关闭Disruptor
disruptor.shutdown();
}
/**
* 单个生产者和消费者的模式
*/
public static void producer() throws InterruptedException {
// 事件生成工厂
LogEventFactory logEventFactory = new LogEventFactory();
//用于生成RingBuffer大小,其大小必须是2的n次方
int ringBufferSize = 2 << 3;
//定义Disruptor初始化信息
Disruptor disruptor = new Disruptor<>(logEventFactory, ringBufferSize, Executors.defaultThreadFactory(), ProducerType.SINGLE, new YieldingWaitStrategy());
//定义处理事件的消费者
disruptor.handleEventsWith(new LogEventConsumer("1"));
//定义事件的开始
disruptor.start();
//获取RingBuffer
RingBuffer ringBuffer = disruptor.getRingBuffer();
//进行事件的发布
LogEventProducer logEventProducer = new LogEventProducer(ringBuffer);
for (int i = 0; i < 10; i++) {
logEventProducer.onData(i, "logEventProducer" + i, new Date());
}
Thread.sleep(1000);
//关闭Disruptor
disruptor.shutdown();
}
}
参考:
disruptor的简单介绍及使用
高性能线程间队列 DISRUPTOR 简介
浅谈Disruptor
剖析Disruptor:为什么会这么快?(一)Ringbuffer的特别之处
剖析Disruptor:为什么会这么快?(一)锁的缺点
剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充
剖析Disruptor:为什么会这么快?(三)揭秘内存屏障
高性能队列——Disruptor
还在用BlockingQueue?读这篇文章,了解下Disruptor吧