扯一下读书
自从实习以后,我对读书是越来越情有独钟了,特别是一些有关时间管理,逻辑思维训练,心理学之类的书。读书是我工作和生活之外的一种补充,是一种调节剂。至于社会上充斥的一些"读书无用论"的观点,我觉得一笑而过就算了。借用三毛姐的一句话,就是:"读书多了,容颜自然改变,许多时候,自己可能以为许多看过的书籍都成为了过眼云烟,不复记忆,其实它们是潜在的。在气质里,在谈吐上,在胸襟的无涯,当然也可能显露在生活和文字里。当读书的习惯成为自然后,你就不会去思考它的用处了"。
背景
高并发是指通过设计保证系统能够同时并行处理很多请求。虽然我在工作中经常听到高并发,QPS之类的术语。其实,我对高并发也是一知半解,知道Java里面可以用Lock,Synchronized,ArrayBlockingQueue之类的来进行高并发的处理。我个人觉得,高并发领域更多的依靠的是经验的累积。今天想跟大家分享的是一个高性能的并发框架---Disruptor。
Disruptor并发框架(Disruptor版本为3.2.1)
目录
- 目标
- Disruptor概述
- 一个官网的简单的demo
- maven依赖
- 创建数据实体类LongEvent
- 创建工厂类LongEventFactory
- 创建消费者类LongEventHandler
- 创建生产者类LongEventProducer
- 创建测试类 LongEventMain
- demo结果输出
- Disruptor的一些核心介绍
- RingBuffer
- 消费者等待策略
- Disruptor的应用场景
一 目标
了解Disruptor的基本概念并且实现一个官网的demo。
二 Disruptor概述
- Disruptor是一个异步并发处理框架。是由LMAX公司开发的一款高效的无锁内存队列。它使用无锁的方式实现了一个环形队列,非常适合于实现生产者和消费者模式,比如事件和消息的发布。
- Disruptor最大特点是高性能,其LMAX架构可以获得每秒6百万订单,用1微秒的延迟获得吞吐量为100K+。
三 一个官网的简单的demo
1.maven依赖
maven引入Disruptor的jar包,Disruptor版本为3.2.1
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.2.1</version>
</dependency>
2.创建数据实体类LongEvent
创建数据实体类LongEvent
//代表数据的类
public class LongEvent
{
private long value;
public void set(long value)
{
this.value = value;
}
}
3.创建工厂类LongEventFactory
创建工厂类LongEventFactory
//产生LongEvent的工厂类,它会在Disruptor系统初始化时,构造所有的缓冲区中的对象实例(预先分配空间)
public class LongEventFactory implements EventFactory<LongEvent>
{
public LongEvent newInstance()
{
return new LongEvent();
}
}
4. 创建消费者类LongEventHandler
创建消费者类LongEventHandler
//消费者实现为WorkHandler接口,是Disruptor框架中的类
public class LongEventHandler implements EventHandler<LongEvent>
{
//onEvent()方法是框架的回调用法
public void onEvent(LongEvent event, long sequence, boolean endOfBatch)
{
System.out.println("Event: " + event);
}
}
5. 创建生产者类LongEventProducer
创建生产者类LongEventProducer
public class LongEventProducer
{
//环形缓冲区,装载生产好的数据;
private final RingBuffer<LongEvent> ringBuffer;
public LongEventProducer(RingBuffer<LongEvent> ringBuffer)
{
this.ringBuffer = ringBuffer;
}
//将数据推入到缓冲区的方法:将数据装载到ringBuffer
public void onData(ByteBuffer bb)
{
long sequence = ringBuffer.next(); // Grab the next sequence //获取下一个可用的序列号
try
{
LongEvent event = ringBuffer.get(sequence); // Get the entry in the Disruptor //通过序列号获取空闲可用的LongEvent
// for the sequence
event.set(bb.getLong(0)); // Fill with data //设置数值
}
finally
{
ringBuffer.publish(sequence); //数据发布,只有发布后的数据才会真正被消费者看见
}
}
}
6. 创建测试类 LongEventMain
创建测试类 LongEventMain
public class LongEventMain
{
public static void main(String[] args) throws Exception
{
// Executor that will be used to construct new threads for consumers
//创建线程池
Executor executor = Executors.newCachedThreadPool();
// The factory for the event
//事件工厂
LongEventFactory factory = new LongEventFactory();
// Specify the size of the ring buffer, must be power of 2.
//ringBuffer 的缓冲区的大小是1024
int bufferSize = 1024;
// Construct the Disruptor
//创建一个disruptor
//ProducerType.MULTI:创建一个环形缓冲区支持多事件发布到一个环形缓冲区
Disruptor<LongEvent> disruptor = new Disruptor<>(factory, bufferSize, executor,ProducerType.MULTI,new BlockingWaitStrategy());
// Connect the handler
//创建一个消费者
disruptor.handleEventsWith(new LongEventHandler());
// Start the Disruptor, starts all threads running
//启动并初始化disruptor
disruptor.start();
// Get the ring buffer from the Disruptor to be used for publishing.
//获取已经初始化好的ringBuffer
RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
获取已经初始化好的ringBuffer
LongEventProducer producer = new LongEventProducer(ringBuffer);
ByteBuffer bb = ByteBuffer.allocate(8);
for (long l = 0; true; l++)
{
//存入数据
bb.putLong(0, l);
producer.onData(bb);
Thread.sleep(1000);
}
}
}
7.demo结果输出
demo结果输出(因为是一个死循环,会不断的输出)
Event: com.meizu.in.appstore.disruptor3.LongEvent@1223929e
Event: com.meizu.in.appstore.disruptor3.LongEvent@407afb9
Event: com.meizu.in.appstore.disruptor3.LongEvent@237bc42c
Event: com.meizu.in.appstore.disruptor3.LongEvent@7cb5a7bb
Event: com.meizu.in.appstore.disruptor3.LongEvent@720c6dd1
Event: com.meizu.in.appstore.disruptor3.LongEvent@4b4f6e4e
Event: com.meizu.in.appstore.disruptor3.LongEvent@366889af
Disruptor的一些核心介绍
1.RingBuffer
- RingBuffer是其核心,生产者向RingBuffer中写入元素,消费者从RingBuffer中消费元素。
- 随着你不停地填充这个buffer(可能也会有相应的读取),这个序号会一直增长,直到绕过这个环。
- 槽的个数是2的N次方更有利于基于二进制的计算机进行计算。(注:2的N次方换成二进制就是1000,100,10,1这样的数字, sequence & (array length-1) = array index,比如一共有8槽,3&(8-1)=3,HashMap就是用这个方式来定位数组元素的,这种方式比取模的速度更快。)
- 会预先分配内存,可以做到完全的内存复用。在系统的运行过程中,不会有新的空间需要分配或者老的空间需要回收。因此,可以大大减少系统分配空间以及回收空间的额外开销。
- 关于RingBuffer可以直观的看一下下面的这幅图片(网上copy的),表示取到编号为4的数据。
2.消费者等待策略
- BlockingWaitStrategy:这是默认的策略。使用锁和条件进行数据的监控和线程的唤醒。因为涉及到线程的切换,是最节省CPU,但在高并发下性能表现最糟糕的一种等待策略。
- SleepingWaitStrategy:会自旋等待数据,如果不成功,才让出cpu,最终进行线程休眠,以确保不占用太多的CPU数据,因此可能产生比较高的平均延时。比较适合对延时要求不高的场合,好处是对生产者线程的影响最小。典型的应用场景是异步日志。
- YieldingWaitStrategy:用于低延时的场合。消费者线程不断循环监控缓冲区变化,在循环内部,会使用Thread.yield()让出cpu给别的线程执行时间。
- BusySpinWaitStrategy:开启的是一个死循环监控,消费者线程会尽最大努力监控缓冲区变化,因此,CPU负担比较大
3.Disruptor的应用场景
Disruptor号称能够在一个线程里每秒处理 6 百万订单,实际上我也没有测试过。Disruptor实际上内部是使用环形队列来实现的,所以一般来说,在消费者和生产者的场景中都可以考虑使用Disruptor。比如像日志处理之类的。实际上,我个人觉得Disruptor就像是Java里面的ArrayBlockingQueue的替代者,因为Disruptor可以提供更高的并发度和吞吐量。从下面这幅官网的图就可以直观的感受到Disruptor和ArrayBlockingQueue之间的效率的对比。(注意这是一个对数对数尺度,不是线性的。)
感谢&总结
在此首先感谢公司同事关于Disruptor的分享。关于本文主要分享了如何去应用Disruptor并发框架,并且对一些核心原理进行了阐述。古人说,"日拱一卒无有尽 功不唐捐终入海",每天进步一点点,量变引起质变,最后,我们还是可以取得长足的进步的,共勉。