构建高性能服务(三)Java高性能缓冲设计 vs Disruptor vs LinkedBlockingQueue
一个仅仅部署在4台服务器上的服务,每秒向Database写入数据超过100万行数据,每分钟产生超过1G的数据。而每台服务器(8核12G)上CPU占用不到100%,load不超过5。这是怎么做到呢?下面将给你描述这个架构,它的核心是一个高效缓冲区设计,我们对它的要求是:
1,该缓存区要尽量简单
2,尽量避免生产者线程和消费者线程锁
3,尽量避免大量GC
缓冲 vs 性能瓶颈提高硬盘写入IO的银弹无疑是批量顺序写,无论是在业界流行的分布式文件系统或数据,HBase,GFS和HDFS,还是以磁盘文件为持久化方式的消息队列Kafka都采用了在内存缓存数据然后再批量写入的策略。这一个策略的性能核心就是内存中缓冲区设计。这是一个经典的数据产生者和消费者场景,缓冲区的要求是当同步写入和读出时:(1)写满则不写(2)读空则不读(3)不丢失数据(4)不读重复数据。最直接也是常用的方式就是JDK自带的LinkedBlockingQueue。LinkedBlockingQueue是一个带锁的消息队列,写入和读出时加锁,完全满缓冲区上面的四个要求。但是当你的程序跑起来之后,看看那个线程CPU消耗最高?往往就是在线程读LinkedBlockingQueue锁的时候,这也成为很多对吞吐要求很高的程序的性能瓶颈。
Disruptor解决加锁队列产生的性能问题?Disruptor是一个选择。Disruptor是什么?看看开源它的公司LMAX自己是怎么介绍的:
?
我们花费了大量的精力去实现更高性能的队列,但是,事实证明队列作为一种基础的数据结构带有它的局限性——在生产者、消费者、以及它们的数据存储之间的合并设计问题。Disruptor就是我们在构建这样一种能够清晰地分割这些关注问题的数据结构过程中所诞生的成果。
?
OK,Disruptor是用来解决我们这个场景的问题的,而且它不是队列。那么它是什么并且如何实现高效呢?我这里不做过多介绍,网上类似资料很多,简单的总结:
1,Disruptor使用了一个RingBuffer替代队列,用生产者消费者指针替代锁。
2,生产者消费者指针使用CPU支持的整数自增,无需加锁并且速度很快。Java的实现在Unsafe package中。
?
使用Disruptor,首先需要构建一个RingBuffer,并指定一个大小,注意如果RingBuffer里面数据超过了这个大小则会覆盖旧数据。这可能是一个风险,但Disruptor提供了检查RingBuffer是否写满的机制用于规避这个问题。而且根据maoyidao测试结果,写满的可能性不大,因为Disrutpor确实高效,除非你的消费线程太慢。
?
并且使用一个单独的线程去处理RingBuffer中的数据:
?
public class ThreadLocalBoundedMQ {private long lastFlushTime=0L;private byte[][] msgs=new byte[Constants.BATCH_INS_COUNT][];private int offset=0;public byte[][] getMsgs(){return msgs;}public void addMsg(byte[] msg){msgs[offset++]=msg;}public int size() {return offset;}public void clear() {offset=0;lastFlushTime=System.currentTimeMillis();}public boolean needFlush(){return (System.currentTimeMillis()-lastFlushTime > Constants.MAX_BUFFER_TIME)&& offset>0;}}实际测试和上线效果良好(效果见本文第一节)!
总结能够使用最简化的代码完成性能和业务要求,是最完美的方法。根据使用场景,你可以有很多假设,但不要被眼花缭乱的新技术迷惑而拿你自己的服务做小白鼠,最适合的,最简单的,就是最好的。
?
本文系maoyidao原创,转载请引用原链接:
http://maoyidao.iteye.com/blog/1663193
同时推荐本系列前2篇
?
构建高性能服务(一)ConcurrentSkipListMap和链表构建高性能Java Memcached
http://maoyidao.iteye.com/blog/1559420
构建高性能服务(二)java高并发锁的3种实现
http://maoyidao.iteye.com/blog/1563523