基于Disruptor游戏服务器消息总线的设计
LMAX是一种新型零售金融交易平台,它能够以很低的延迟(latency)产生大量交易(吞吐量). 这个系统是建立在JVM平台上,核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单,用1微秒的延迟获得吞吐量为100K . 业务逻辑处理器完全是运行在内存中(in-memory),使用事件源驱动方式(event sourcing). 业务逻辑处理器的核心是Disruptors。Disruptor是一个高性能低延迟的异步处理框架,或者可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式实现,或者事件-监听模式的实现,直接称disruptor模式。
我们原先的设计是基于LinkedBlockingQueue进行投递的,生产和消费都用到了锁,生产用的是putLock,消费是takeLock。替换成Disruptor后,生产时锁消失,消费时由于用的是BlockingWaitStrategy策略,所以还存在锁,但由于是单线程消费所以,基本没什么锁竞争的消耗。
以下是测试测试环境:
model name | Intel(R) Xeon(R) CPU E3-1230 V2 @ 3.30GHz |
cpu core | 8 |
cache size | 15360 KB |
OS | Windows 7 64bit |
JDK | 1.8.60 |
Disruptor | 3.3.2 |
ringBufferSize | 33554432 |
producerType | ProducerType.MULTI |
WaitStrategy | BlockingWaitStrategy |
从消息处理量进行对比:
处理的消息数量:32*1024*1024=33554432
耗时对比:
Duration(ms) | LinkedBlockingQueue | Disruptor | 对比 |
1 | 5771 | 4209 | 73% |
2 | 5725 | 3479 | 60.77% |
3 | 5433 | 3228 | 59.41% |
4 | 5297 | 2805 | 52.95% |
5 | 5745 | 2477 | 43.12% |
6 | 6124 | 2428 | 39.65% |
QPS | LinkedBlockingQueue | Disruptor | 对比 |
1 | 5814318 | 7972067 | 137% |
2 | 5861036 | 9644850 | 165% |
3 | 6176041 | 10394805 | 168% |
4 | 6334611 | 11962364 | 189% |
5 | 5840632 | 13546400 | 232% |
6 | 5479169 | 13819783 | 252% |
分析:当线程较少时,性能提升并不是很明显,单线程时耗时降为原先的73%,QPS提升到原先的137%,随着线程数增多,性能提升幅度越来越大,当生产者为六根线程时,耗时已经降为了原先的39.65%,QPS处理量已经达到了252%,可以预见,线程越多,性能提升越大。
而LinkedBlockingQueue,当线程数<=4时,随着线程数增多,多线程的处理消息的正面效果大于锁竞争带来的负面效果,所以性能慢慢提升,但超过5根后,效果反转,性能表现越来越差,可以预见,线程越多,性能会越来越差,在各方面表现上都完败Disruptor.
从CPU占用来看:
LinkedblockedQueue压测时的CPU表现:
Disruptor压测时的CPU表现:
分析LinkedblockedQueue的CPU占用在30%~35%上下浮动,Disruptor的CPU占用在25%~30%上下浮动,CPU占用降低5%左右占用。
线程CPU耗时对比:
分析:
第1组:LinkedblockedQueue-produce-1 和 :LinkedblockedQueue-produce-2 为生产者,对应的LinkedblockedQueue为消费者;
第2组:Disruptor--produce-1 和 :Disruptor-produce-2 为生产者,对应的Disruptor-1为消费者;
很明显,处理同样的消息量33554432,LBQ耗时达到了Disruptor的2.26倍左右。
最后,再说下消息缓冲区提前分配的问题,以下是网上一段精彩的描述:"避免GC:写Java程序的时候,很多人习惯随手new各种对象,虽然Java的GC会负责回收,但是系统在高压力情况下频繁的new必定导致更频繁的GC,Disruptor避免这个问题的策略是:提前分配。在创建RingBuffer实例时,参数中要求给出缓冲区元素类型的Factory,创建实例时,Ring Buffer会首先将整个缓冲区填满为Factory所产生的实例,后面生产者生产时,不再用传统做法(顺手new一个实例出来然后add到buffer中),而是获得之前已经new好的实例,然后设置其中的值。举个形象的例子就是,若缓冲区是个放很多纸片的地方,纸片上记录着信息,以前的做法是:每次加入缓冲区时,都从系统那现准备一张纸片,然后再写好纸片放进缓冲区,消费完就随手扔掉。现在的做法是:实现准备好所有的纸片,想放入时只需要擦掉原来的信息写上新的即可"
由于压力测试过程中,会瞬间创建海量消息(33554432),所以导致年老代将近被占满而导致会不断的进行fullGC,所以压测时,务必要调大内存分配,尽量少让FULLGC来影响测试结果,毕竟生产环境不会需要这么大的消息缓存的(目前生产环境容量仅为压测时的1/100)。
总之,优化后,消息总线的处理性能至少翻倍。
http://blog.csdn.net/jiangguilong2000/article/details/49493105