目录
1. 为什么要不选择BlockigQueue做生产者消费者缓存队列。
2. Disruptor的优势
3. 认识RingBuffer
4. 术语说明
5. 开发示例
6. 提高消费者的响应时间
1、为什么要不选择BlockigQueue做生产者消费者缓存队列。
BlockigQueue用于实现生产者和消费者是一个很不错的选择。它可以很方便的实现生产者和消费者之间的数据缓冲区。但是BlockigQueue我们我们知道他是使用锁和阻塞机制的,因此使用BlockigQueue实现生产者和消费并不是一个高性能的实现。但这并不是说他不好,只是说在高并发场合,它的性能并不是特别的优越,是一个带锁阻塞的生产者-消费者模式。
2、Disruptor的优势
Disruptor它是一个开源的并发框架,并获得2011 Duke’s 程序框架创新奖,能够在无锁的情况下实现网络的Queue并发操作。
Disruptor和BlockigQueue相比,Disruptor是一个无锁竞争的、异步的、高性能,或者是可以认为是最快的消息框架(轻量级的JMS),所以他非常合适实现生产者和消费者模式,比如消息的发布。
Disruptor底层存储数据的方式是使用环形队列(RingBuffer)实现的,它和传统的线性队列(BlockigQueue)相比,传统线性队列需要同时维护首部指针和尾部指针。而 RingBuffer它最大特点是没有尾部指针
,只需要维护一个指向下一个可用元素的指针,实现进出队列。这样极大的优化了线程协作的复杂度(大家自己在写线程直接的协作工作的时候,都知道要对临界区进行加锁,等待,唤醒等操作,这在高并发是很消耗性能的)。
那么Disruptor的效率到底有多高。Martin Fowler在自己网站上写了一篇LMAX架构的文章,在文章中他介绍了LMAX是一种新型零售金融交易平台,它能够以很低的延迟产生大量交易。这个系统是建立在JVM平台上,其核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单
。业务逻辑处理器完全是运行在内存中,使用事件源驱动方式。业务逻辑处理器的核心是Disruptor。
3、认识RingBuffer
1)RingBuffer,它是Disruptor的核心,那什么是RingBuffer。前面已经提供提是一个环形的数据结构,她的作用就是存储数据,实现不同线程之间的协同工作传递数据。
如下图: 环形RingBuffer的结构
2)RingBuffer每块区是拥有一个序号的,这个序号指向环形数组结构的下一个可用元素。
如下图:
3)随着不断地写进了填充这个圆环,这个指针序号会不断地递增,直到绕过这个环。
如下图:
当这个圆环满了之后,不会删除RingBuffer里面的数据,也就是说数据会一直存放在RingBuffer中。那么你可能就会有疑问了,如果继续有新数据到来怎么办法?
如果继续有新数据到来,那么它会把原来的旧数据覆盖,什么意思呢?
如上图:现在12的区域的下个区域目前是3,如果有新的数据到来,那么指针往下移的时候就会把区域3的数据给覆盖变成13。
当然,框架提供了一系列帮助我们平行消费的监控,会很好的控制生产者和消费者之间的速度,从而达到生产和消费之间的平衡。
4)RingBuffer之所以效率高,它还下面的原因。
- RingBuffer是数组的,定位也是非常迅速,所以自然会比链表结构效率高。
- 数组的内存分配是预先加载的,一但指定大小创建后,就一直存在。这也意味着不需要花大量的时间做垃圾回收。而链表结构需要为每个对象创建节点,然后删除节点,然后执行内存清理等。
4、术语说明
- Sequence:采用缓存行填充的方式对long类型的一层包装,用以代表事件的序号。通过unsafe的cas方法从而避免了锁的开销;
- Sequencer:生产者与缓存RingBuffer之间的桥梁。单生产者与多生产者分别对应于两个实现SingleProducerSequencer与MultiProducerSequencer。Sequencer用于向RingBuffer申请空间,使用publish方法通过waitStrategy通知所有在等待可消费事件的SequenceBarrier;
- WaitStrategy:WaitStrategy有多种实现,用以表示当无可消费事件时,消费者的等待策略;
- SequenceBarrier:消费者与缓存RingBuffer之间的桥梁。消费者并不直接访问RingBuffer,从而能减少RingBuffer上的并发冲突;
- EventProcessor:事件处理器,是消费者线程池Executor的调度单元,是对事件处理EventHandler与异常处理ExceptionHandler等的一层封装;
- Event: 消费事件。Event的具体实现由用户定义;
- RingBuffer:基于数组的缓存实现,也是创建sequencer与定义WaitStrategy的入口;
- Disruptor:Disruptor的使用入口。持有RingBuffer、消费者线程池Executor、消费者集合ConsumerRepository等引用。
5、生产者-消费(开发示例)
Mvaen开发,所需配置
<!-- disruptor高并发框架 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
Main函数
import java.nio.ByteBuffer;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.lmax.disruptor.EventFactory;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SequenceBarrier;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.WorkerPool;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.ProducerType;
public class Main {
public static void main(String[] args) throws Exception {
//创建RingBuffer
RingBuffer<Order> ringBuffer = RingBuffer.create(ProducerType.MULTI, //生产类型,MULTI表示可以多个生产者
new EventFactory<Order>() {
@Override
public Order newInstance() {
return new Order();
}
},
1024 * 1024,
new YieldingWaitStrategy());
//构建消费者与缓存RingBuffer之间的桥梁。消费者并不直接访问RingBuffer,从而能减少RingBuffer上的并发冲突。
SequenceBarrier barriers = ringBuffer.newBarrier();
//创建多个消费者
Consumer[] consumers = new Consumer[3];
for(int i = 0; i < consumers.length; i++){
consumers[i] = new Consumer("c" + i);
}
WorkerPool<Order> workerPool =
new WorkerPool<Order>(ringBuffer,
barriers,
new IntEventExceptionHandler(),
consumers);
//将消费进度仍给生产者监控,用来平衡生产消费
ringBuffer.addGatingSequences(workerPool.getWorkerSequences());
ExecutorService executor =Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//执行消费
workerPool.start(executor);
final CountDownLatch latch = new CountDownLatch(1);
//创建生产者
for (int i = 0; i < 100; i++) {
final Producer p = new Producer(ringBuffer);
new Thread(new Runnable() {
@Override
public void run() {
try {
//使当前线程等待,直到锁向下计数为零,除非线程 interrupted。
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j = 0; j < 100; j ++){
p.onData(UUID.randomUUID().toString());
}
}
}).start();
}
Thread.sleep(2000);
System.out.println("---------------开始生产-----------------");
latch.countDown();
Thread.sleep(5000);
System.out.println("总数:" + consumers[0].getCount() );
}
//异常处理器
static class IntEventExceptionHandler implements ExceptionHandler {
@Override
public void handleEventException(Throwable ex, long sequence, Object event) {}
@Override
public void handleOnStartException(Throwable ex) {}
@Override
public void handleOnShutdownException(Throwable ex) {}
}
}
生产者
import java.nio.ByteBuffer;
import java.util.UUID;
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.RingBuffer;
//生产者
public class Producer {
private final RingBuffer<Order> ringBuffer;
public Producer(RingBuffer<Order> ringBuffer){
this.ringBuffer = ringBuffer;
}
/**
* onData用来发布事件,每调用一次就发布一次事件
* 它的参数会用过事件传递给消费者
*/
public void onData(String data){
//可以把ringBuffer看做一个事件队列,那么next就是得到下面一个事件槽
long sequence = ringBuffer.next();
try {
//用上面的索引取出一个空的事件用于填充(获取该序号对应的事件对象)
Order order = ringBuffer.get(sequence);
//获取要通过事件传递的业务数据
order.setId(data);
} finally {
//发布事件
//注意,最后的 ringBuffer.publish 方法必须包含在 finally 中以确保必须得到调用;如果某个请求的 sequence 未被提交,将会堵塞后续的发布操作或者其它的 producer。
ringBuffer.publish(sequence);
}
}
}
消费者
import java.util.concurrent.atomic.AtomicInteger;
import com.lmax.disruptor.WorkHandler;
//消费者,WorkHandler接口来自框架
public class Consumer implements WorkHandler<Order>{
private String consumerId;
//计算器
private static AtomicInteger count = new AtomicInteger(0);
public Consumer(String consumerId){
this.consumerId = consumerId;
}
@Override
public void onEvent(Order order) throws Exception {
//这里是消费逻辑,onEvent()方法回进行回到,数据读取全部已经由框架负责封装。
System.out.println("当前消费者: " + this.consumerId + ",消费信息:" + order.getId());
count.incrementAndGet();
}
public int getCount(){
return count.get();
}
}
将被生产的产品
import lombok.Data;
//订单
@Data
public class Order {
private String id;//ID
private String name;//名称
private double price;//金额
}
6、WaitStrategy可选的等待策略,提高消息者的等待时间
当数据在环形缓存区(RingBuffer)的产生时,消息者如何知道这些新产生的数据呢?就是通过WaitStrategy策略来监控的,为此,Disruptor提供了几种策略,这些策略由WaitStrategy接口进行封装,主要有以下几种:
BlockingWaitStrategy
这是默认的策略。使用 BlockingWaitStrategy 和使用BlockingQueue非常类似,它们都使用锁和条件(Conditon)进行数据的监控和线程的唤醒。因为涉及到线程切换,BlockingWaitStrategy策略是最节省CPU,但是高并发下性能表现最糟糕的一种等待策略。
SleepingWaitStrategy
这策略也是对cpu非常保守的。它会循环中不断等待数据。和BlockingWaitStrategy一样,SpleepingWaitStrategy的CPU使用率也比较低。它的方式是循环等待并且在循环中间调用LockSupport.parkNanos(1)来睡眠,(在Linux系统上面睡眠时间60µs).然而,它的优点在于生产线程只需要计数,而不执行任何指令。并且没有条件变量的消耗。但是,事件对象从生产者到消费者传递的延迟变大了。SleepingWaitStrategy最好用在不需要低延迟,而且事件发布对于生产者的影响比较小的情况下。比如异步日志功能。
YieldingWaitStrategy
这个策略用于低延迟时的场合。YieldingWaitStrategy是可以被用在低延迟系统中的两个策略之一,这种策略在减低系统延迟的同时也会增加CPU运算量。YieldingWaitStrategy策略会循环等待sequence增加到合适的值。循环中调用Thread.yield()允许其他准备好的线程执行。如你需要一个高性能系统,并且延时有较为严格的要求,则可以考虑这种测试。例如:在开启超线程的时候。
BusySpinWaitStrategy
BusySpinWaitStrategy是性能最高,最疯狂的等待策略,同时也是对部署环境要求最高的策略,底层是一个死循环,消费线程会尽最大的努力去监控RingBuffer的变化。这个性能最好用在事件处理线程比物理内核数目还要小的时候。例如:在禁用超线程技术的时候。
参考书籍:java高并发程序设计
参考资料:并发编程网