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

本次代码测试基于相同的 容量、生产线程数、单个线程生产量; 仅有一个消费线程。

修改各参数得到的结果:

数据规模、并发线程数、 最主要的是容量小时:Disruptor没有优势

2019-08-29T07:42:35.235Z  线程数:64 单线程生产量: 2048 容量:32 数据总量:131072
2019-08-29T07:42:48.742Z  EventProcessor wrapper
2019-08-29T07:42:48.743Z  disruptor start success!
2019-08-29T07:42:51.113Z  处理的sequence:131071 count:131072  Disruptor 总耗时:2369

2019-08-29T07:42:36.200Z  ArrayBlockingQueue 生产耗时:962
2019-08-29T07:42:36.200Z  处理count:131072  ArrayBlockingQueue 消费耗时:962
2019-08-29T07:42:36.201Z  ArrayBlockingQueue 总耗时:963
2019-08-29T08:24:38.641Z  线程数:512 单线程生产量: 2048 容量:32 数据总量:1048576
2019-08-29T08:24:38.670Z  EventProcessor wrapper
2019-08-29T08:24:38.670Z  disruptor start success!
2019-08-29T08:25:08.590Z  处理的sequence:1048575 count:1048576  Disruptor 总耗时:29918

2019-08-29T08:25:54.753Z  处理count:1048576  ArrayBlockingQueue 消费耗时:9231
2019-08-29T08:25:54.753Z  ArrayBlockingQueue 生产耗时:9230
2019-08-29T08:25:54.753Z  ArrayBlockingQueue 总耗时:9231

增大容量:  Disruptor的性能上升

2019-08-29T07:40:28.980Z  线程数:64 单线程生产量: 2048 容量:128 数据总量:131072
2019-08-29T07:40:29.008Z  EventProcessor wrapper
2019-08-29T07:40:29.008Z  disruptor start success!
2019-08-29T07:40:29.694Z  处理的sequence:131071 count:131072  Disruptor 总耗时:685

2019-08-29T07:47:42.436Z  处理count:131072  ArrayBlockingQueue 消费耗时:508
2019-08-29T07:47:42.436Z  ArrayBlockingQueue 生产耗时:508
2019-08-29T07:47:42.436Z  ArrayBlockingQueue 总耗时:508
2019-08-29T07:43:39.073Z  线程数:64 单线程生产量: 2048 容量:512 数据总量:131072
2019-08-29T07:43:39.101Z  EventProcessor wrapper
2019-08-29T07:43:39.101Z  disruptor start success!
2019-08-29T07:43:39.269Z  处理的sequence:131071 count:131072  Disruptor 总耗时:167

2019-08-29T07:43:53.722Z  ArrayBlockingQueue 生产耗时:383
2019-08-29T07:43:53.722Z  处理count:131072  ArrayBlockingQueue 消费耗时:383
2019-08-29T07:43:53.722Z  ArrayBlockingQueue 总耗时:383
2019-08-29T07:44:05.995Z  线程数:64 单线程生产量: 2048 容量:1024 数据总量:131072
2019-08-29T08:18:10.426Z  EventProcessor wrapper
2019-08-29T08:18:10.426Z  disruptor start success!
2019-08-29T08:18:10.524Z  处理的sequence:131071 count:131072  Disruptor 总耗时:97

2019-08-29T07:44:06.365Z  ArrayBlockingQueue 生产耗时:367
2019-08-29T07:44:06.365Z  处理count:131072  ArrayBlockingQueue 消费耗时:367
2019-08-29T07:44:06.365Z  ArrayBlockingQueue 总耗时:367

再增大各指标参数: Disruptor优势越来越明显

2019-08-29T07:50:59.911Z  线程数:64 单线程生产量: 65536 容量:1048576 数据总量:4194304
2019-08-29T07:51:28.075Z  EventProcessor wrapper
2019-08-29T07:51:28.075Z  disruptor start success!
2019-08-29T07:51:28.577Z  处理的sequence:4194303 count:4194304  Disruptor 总耗时:501

2019-08-29T07:51:11.549Z  ArrayBlockingQueue 生产耗时:11633
2019-08-29T07:51:11.575Z  处理count:4194304  ArrayBlockingQueue 消费耗时:11659
2019-08-29T07:51:11.575Z  ArrayBlockingQueue 总耗时:11659
2019-08-29T07:57:22.994Z  线程数:128 单线程生产量: 65536 容量:1048576 数据总量:8388608
2019-08-29T07:57:23.074Z  EventProcessor wrapper
2019-08-29T07:57:23.074Z  disruptor start success!
2019-08-29T07:57:24.036Z  处理的sequence:8388607 count:8388608  Disruptor 总耗时:961

2019-08-29T07:58:25.567Z  ArrayBlockingQueue 生产耗时:47941
2019-08-29T07:58:25.646Z  处理count:8388608  ArrayBlockingQueue 消费耗时:48020
2019-08-29T07:58:25.647Z  ArrayBlockingQueue 总耗时:48021

再大线程数, ArrayBlockingQueue 更耗时了,而Disruptor仍旧很快

2019-08-29T08:05:17.927Z  线程数:256 单线程生产量: 65536 容量:1048576 数据总量:16777216
2019-08-29T08:05:18.026Z  EventProcessor wrapper
2019-08-29T08:05:18.027Z  disruptor start success!
2019-08-29T08:05:20.060Z  处理的sequence:16777215 count:16777216  Disruptor 总耗时:2032

经测试发现: 

  1. 容量大小 与 消费者的消费速度  与整个耗时 成正比。
  2. Disruptor的性能在高并发、高数据规模(bufferSize 要大些)时表现更突出。
  3.  Disruptor与LinkedBlockingQueue(比ArrayBlockingQueue性能好些)比对而言,当bufferSize大些的时候,也有优势。 

测试入口

package com.lmax.disruptor.noob;

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

/**
 * 担心影响, 分开执行测试
 * 
 * @author admin
 *
 */
public class CompareTest {
	public static int THREAD = 2 << 8; // 线程数量
	public static int PER = 2 << 10; // 单个线程生产数量
	public static int TOTAL_COUNT = THREAD * PER; // 数据总量
	public static int SIZE =32; // 最大容量

	public static void main(String[] args) {
		println("线程数:" + THREAD + " 单线程生产量: " + PER + " 容量:" + SIZE + " 数据总量:" + TOTAL_COUNT);
		 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.SIZE);

		AtomicBoolean endP = new AtomicBoolean(false);
		AtomicBoolean endC = new AtomicBoolean(false);
		long startTime = System.currentTimeMillis();
		AtomicLong count = new AtomicLong(0);
		for (int i = 0; i < CompareTest.THREAD; i++) {
			final int m = i;
			new Thread(() -> {
				for (int j = 0; j < CompareTest.PER; j++) {
					try {
						queue.put("i" + m + "j" + j); // 队列不够,等待生产
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (count.incrementAndGet() == CompareTest.TOTAL_COUNT) {
						CompareTest.println("ArrayBlockingQueue 生产耗时:" + (System.currentTimeMillis() - startTime));
						endP.set(true);
					}
				}
			}).start();
		}

		new Thread(() -> {
			AtomicLong consumerCount = new AtomicLong(0);
			while (true) {
				try {
					queue.take(); // 直到消费完所有信息
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (consumerCount.incrementAndGet() == CompareTest.TOTAL_COUNT) {
					break;
				}
			}
			CompareTest.println("处理count:" + consumerCount.get() + "  ArrayBlockingQueue 消费耗时:"
					+ (System.currentTimeMillis() - startTime));
			endC.set(true);
		}).start();

		while (!(endC.get() && endP.get())) {}
		
       CompareTest.println("ArrayBlockingQueue 总耗时:" + (System.currentTimeMillis() - startTime));

	}
}

Disruptor 测试用例

验证要点

  1. Disruptor 消费与生产是完全异步的。
  2. 因为 当RingBuffer没有空闲空间时,RingBuffer.next()会阻塞生产者的获取sequence;  所以:使用RingBuffer.next()获取一个事件槽,那么一定要发布对应的事件! 如果不能发布事件,那么就会引起Disruptor状态的混乱。那么,很显然这个时候需要调用者在事件处理的实现上来判断事件携带的数据是否是正确的或者完整的,这是实现者应该要注意的事情。
  3. 如果RingBuffer的事件槽的个数是2的N次方更有利于基于二进制的计算机进行计算。
    (ps:2的N次方换成二进制就是1000,100,10,1这样的数字, sequence & (array length-1) = array index,比如一共有8槽,3&(8-1)=3,HashMap就是用这个方式来定位数组元素的,这种方式比取模的速度更快。)
     Disruptor构造函数中,会调用RingBuffer.createMultiProducer创建RingBuffer。RingBuffer内部对象Sequencer -> MultiProducerSequencer的初始化过程执行到AbstractSequencer的构造函数中将判定:    “bufferSize must be a power of 2  ” &  ” bufferSize must not be less than 1 ” 
  4. Disruptor初始化RingBuffer时,在其父类RingBufferFields的构造函数中将调用指定的EventFactory来给entries[] 数组中的每一个index初始化DataEvent(预分配内存)。
    所以,若sequence > BufferSize 时, 获取到的DataEvent是含前次处理数据的
  5. Disruptor 事件处理EventHandler必须在启动之前绑定。 Disruptor调用handleEventsWith来创建EventProcessor<Runnable>时会判定Disruptor已启动则报错。 在Disruptor.start()时,将这个BatchEventProcessor交由默认的BasicExecutor(可自定义)执行,用传入的ThreadFactory封装成Thread执行。所以: 消费者线程由初始化Disruptor时指定的threadFactory创建!
  6. Disruptor.shutdown 方法会堵塞直至所有的事件都得到处理需要主动关闭外部指定的executor 。
    (ps:   
           经过测试发现:shutdown并没有阻塞至所有事件事件得到处理,更准确的说,高并发时仍旧有大量的事件准备置入RingBuffer, shutdown只是循环判定一个瞬时态的事件已经处理完。可能是shutdown在判定Disruptor.hasBacklog方法中,仅仅只是判定RingBuffer的cursor<= 消费者的Last-Sequence就直接shutdown了(类似于此瞬时态下,消费了所有事件)。所以慎用!)
         
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() {

		Disruptor<DataEvent> disruptor = new Disruptor<DataEvent>(new DataEventFactory(), CompareTest.SIZE,
				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());

		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();

		for (int l = 0; l < CompareTest.THREAD; l++) {
			new Thread(() -> {
				for (int m = 0; m < CompareTest.PER; m++) {
					producer.onData(start);
					// translator.onData(start);
				}
			}).start();
		}
		/**
		 * 关闭 disruptor,方法会堵塞,直至所有的事件都得到处理;并不会自动关闭外部指定的executor,需要主动关闭
		 */
		// disruptor.shutdown();
        // 	CompareTest.println("disruptor shutdown success!");
		// executor.shutdown();
	}
}

事件

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/finally保证事件一定会被发布)。
 * 如果我们使用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创建的
		 */
		if (count.incrementAndGet() == CompareTest.TOTAL_COUNT) {
			CompareTest.println("处理的sequence:" + sequence + " count:" + count.get() + "  Disruptor 总耗时:"
					+ (System.currentTimeMillis() - event.getStartTime()));
		}
	}

}

 

转载于:https://my.oschina.net/u/3434392/blog/3099183

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值