Disruptor (3) - 与ArrayBlockingQueue的性能比对

拈花ヽ惹草 提交于 2019-11-28 21:59:22

本次代码测试基于相同的 容量、生产者线程、单个生产者生产量、单个消费线程。

测试结果:

 

经测试证明:

  1. 在容量达到阀值时, Disruptor的性能更突出
  2. RingBuffer 的大小 与 消费者的消费速度 都直接影响到整个耗时。

测试入口

package com.lmax.disruptor.noob;

import java.time.Instant;
import java.time.format.DateTimeFormatter;

public class CompareTest {
	public static int THREAD = 2 << 10; // 线程数量
	public static int PER = 2 << 10; // 单个线程生产数量
	public static int CAP = 256; // 最大容量

	public static void main(String[] args) {
		println("生产线程数:" + THREAD + " 单个线程生产量: " + PER + " 最大容量:" + CAP);
		new Thread(() -> ArrayBlockingQueueTest.execute()).start();
		new Thread(() -> DisruptorTest.execute()).start();
	}

	public static void println(String msg) {
		System.out.println(DateTimeFormatter.ISO_INSTANT.format(Instant.now()) + "  " + msg);
	}
}

ArrayBlockingQueue 测试用例

package com.lmax.disruptor.noob;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

public class ArrayBlockingQueueTest {

	public static void execute() {
		ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(CompareTest.CAP);
		AtomicLong count = new AtomicLong();
		AtomicBoolean endP = new AtomicBoolean(false);
		AtomicBoolean endC = new AtomicBoolean(false);
		long startTime = System.currentTimeMillis();
		for (int i = 0; i < CompareTest.THREAD; i++) {
			final int m = i;
			new Thread(() -> {
				for (int j = 0; j < CompareTest.PER; j++) {
					queue.offer("i" + m + "j" + j);
					count.incrementAndGet();
					if (count.get() == CompareTest.CAP) {
						CompareTest.println("ArrayBlockingQueue 生产耗时:" + (System.currentTimeMillis() - startTime));
						endP.set(true);
					}
				}
			}).start();
		}

		new Thread(() -> {
			while (!queue.isEmpty() && count.get() != CompareTest.CAP) {
				queue.poll();
			}
			CompareTest.println("ArrayBlockingQueue 消费耗时:" + (System.currentTimeMillis() - startTime));
			endC.set(true);
		}).start();

		while (!(endC.get() && endP.get())) {

		}
		CompareTest.println("ArrayBlockingQueue 总耗时:" + (System.currentTimeMillis() - startTime));

	}
}

Disruptor 测试用例

验证要点

  1. 因为 当RingBuffer没有空闲空间时,ringBuffer.next()会阻塞生产者的获取sequence。 如果槽的个数是2的N次方更有利于基于二进制的计算机进行计算。
    (校对注:2的N次方换成二进制就是1000,100,10,1这样的数字, sequence & (array length-1) = array index,
    比如一共有8槽,3&(8-1)=3,HashMap就是用这个方式来定位数组元素的,这种方式比取模的速度更快。)
  2. Disruptor 消费与生产是完全异步的。
  3.  如果我们使用RingBuffer.next()获取一个事件槽,那么一定要发布对应的事件。如果不能发布事件,那么就会引起Disruptor状态的混乱
  4. Disruptor 事件处理handler必须在启动之前绑定。 handleEventsWith时子过程Disruptor.checkNotStarted()会判定Disruptor已启动则报错
  5. handleEventsWith 会将EventHandler封装成EventProcessors<Runnable>, 在start()时,将这个BatchEventProcessor用传入的ThreadFactory封装, 扔入默认的BasicExecutor执行
  6. 消费者线程由初始化Disruptor时指定的threadFactory创建的
package com.lmax.disruptor.noob;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;

public class DisruptorTest {
	public static void execute() {
		int bufferSize = CompareTest.CAP;

		Disruptor<DataEvent> disruptor = new Disruptor<DataEvent>(new DataEventFactory(), bufferSize,
				new ThreadFactory() {
					@Override
					public Thread newThread(Runnable eventProcessor) {
						CompareTest.println("EventProcessor wrapper");// 对事件处理总线的封装
						Thread thread = new Thread(eventProcessor);
						thread.setName("EventProcessorWrapper");
						return thread;
					}
				});
		/**
		 * 创建EventProcessors<Runnable>.
		 * 子过程Disruptor.checkNotStarted()事件处理handler必须在启动之前绑定.
		 */
		disruptor.handleEventsWith(new DataEventHandler());
		/**
		 * 将BatchEventProcessor用传入的ThreadFactory封装, 扔入默认的BasicExecutor执行
		 */
		disruptor.start();
		CompareTest.println("disruptor start success!");

		RingBuffer<DataEvent> ringBuffer = disruptor.getRingBuffer();
		DataProducer producer = new DataProducer(ringBuffer);
		DataEventProducerWithTranslator translator = new DataEventProducerWithTranslator(ringBuffer);
		long start = System.currentTimeMillis();

		AtomicLong count = new AtomicLong();
		for (int l = 0; l < CompareTest.THREAD; l++) {
			new Thread(() -> {
				for (int m = 0; m < CompareTest.PER; m++) {
					producer.onData(start);
					// translator.onData(start);
					count.getAndIncrement();
				}
			}).start();
		}
	}
}

事件

package com.lmax.disruptor.noob;

/**
 * 事件实例封装 业务数据传递对象
 * 
 * @author admin
 *
 */
public class DataEvent {
	private long startTime;

	public long getStartTime() {
		return startTime;
	}

	public void setStartTime(long startTime) {
		this.startTime = startTime;
	}

}
---

package com.lmax.disruptor.noob;
import com.lmax.disruptor.EventFactory;

/*
 * 构建传递的数据封装对象, 在初始化ringBuffer时,直接给entries[]每个地址上初始化DataEvent
 */
public class DataEventFactory implements EventFactory {

	@Override
	public Object newInstance() {
		return new DataEvent();
	}

}

生产者

package com.lmax.disruptor.noob;

import com.lmax.disruptor.RingBuffer;

public class DataProducer {
	private final RingBuffer<DataEvent> ringBuffer;

	public DataProducer(RingBuffer<DataEvent> ringBuffer) {
		this.ringBuffer = ringBuffer;
	}

	/**
	 * 当前还是生产线程
	 * <p>
	 * onData用来发布事件,每调用一次就发布一次事件事件 它的参数会通过事件传递给消费者
	 * 
	 * @param data
	 */
	public void onData(long data) {//
		// 可以把ringBuffer看做一个事件队列,那么next就是得到下面一个事件槽, 若没有空闲的时间槽则阻塞
		long sequence = ringBuffer.next();
		// CompareTest.println("生产置入sequence:" + sequence);
		try {
			// 用上面的索引取出一个空的事件用于填充
			DataEvent event = ringBuffer.get(sequence);// for the sequence
			event.setStartTime(data);
		} finally {
			// 发布事件
			ringBuffer.publish(sequence);
		}
	}
}

获取下一个事件槽并发布事件要使用try/finally保证事件一定会被发布, 所以最好直接使用 ringBuffer.publishEvent方式将数据交由Translator来处理填充DataEvent,最后finally发布

package com.lmax.disruptor.noob;

import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.RingBuffer;

/**
 * 获取下一个事件槽并发布事件(发布事件的时候要使用try/finnally保证事件一定会被发布)。
 * 如果我们使用RingBuffer.next()获取一个事件槽,那么一定要发布对应的事件。如果不能发布事件,那么就会引起Disruptor状态的混乱
 * 。尤其是在多个事件生产者的情况下会导致事件消费者失速,从而不得不重启应用才能会恢复。
 * 
 * @author admin
 *
 */
public class DataEventProducerWithTranslator {
	private final RingBuffer<DataEvent> ringBuffer;

	// 一个translator可以看做一个事件初始化器,publicEvent方法会调用它
	// 填充Event
	private static final EventTranslatorOneArg<DataEvent, Long> TRANSLATOR = new EventTranslatorOneArg<DataEvent, Long>() {
		public void translateTo(DataEvent event, long sequence, Long startTime) {
			event.setStartTime(startTime);
		}
	};

	public DataEventProducerWithTranslator(RingBuffer<DataEvent> ringBuffer) {
		this.ringBuffer = ringBuffer;
	}

	public void onData(Long bb) {
		ringBuffer.publishEvent(TRANSLATOR, bb);
		// 当前还是生产者线程
	//	CompareTest.println(Thread.currentThread().getName() + " pulishEvent end!");
		
	}
}

处理者

package com.lmax.disruptor.noob;

import java.util.concurrent.atomic.AtomicLong;

import com.lmax.disruptor.EventHandler;

/**
 * 对指定事件的处理过程
 *
 */
public class DataEventHandler implements EventHandler<DataEvent> {
	public AtomicLong count = new AtomicLong(0);

	@Override
	public void onEvent(DataEvent event, long sequence, boolean endOfBatch) throws Exception {
		/**
		 *  消费者线程由初始化Disruptor时指定的threadFactory创建的
		 */
	//	CompareTest.println(Thread.currentThread().getName() + "----" + event.getStartTime()); 
		count.incrementAndGet();
		if (count.incrementAndGet() == CompareTest.CAP) {
			CompareTest.println("处理的sequence:" + sequence + "  Disruptor 总耗时:"
					+ (System.currentTimeMillis() - event.getStartTime()));

		}
	}

}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!