偶然中看到一篇 关于disruptor的分析,高性能低延迟的特性吸引了我。
disruptor
java实现的线程间通信的高性能低延迟消息组件,也就是消息队列,相比于BlockingQueue 最大的特点在于无锁的机制,性能提高一倍以上,常用于金融行业。
核心机制
1)无锁 CAS
使用了CPU级别的CAS指令,比os级的 lock快很多 ,避免了上下文切换。
2)数组减小GC开销
使用环形数组,而不是链表的数组结构存储数据,数组预分配,避免JavaGC的开销r
3)避免伪共享
数组顺序执行,利用了cpu的缓存特性,不至于 经常缓存失效,重新到内存读,提高缓存命中率。disruptor会尽量将两个不相关的内存隔离到两个缓存行上,不过要牺牲空间。一个缓存行可能128字节或者64字节。可重用大于替换。
- 环形数组结构
为了避免垃圾回收,采用数组而非链表。同时,数组对处理器的缓存机制更加友好。
- 元素位置定位
数组长度2^n,通过位运算,加快定位的速度。下标采取递增的形式。不用担心index溢出的问题。index是long类型,即使100万QPS的处理速度,也需要30万年才能用完。
9 223 372 036 854 775 807
- 无锁设计
每个生产者或者消费者线程,会先申请可以操作的元素在数组中的位置,申请到之后,直接在该位置写入或者读取数据。
核心组件
RingBuffer
通过使用递增的sequence 表示 访问位置,写入位置等。不用访问位置由消费者维护,而ringBuffer 自身维护了一个当前的写入值。通过CAS保证并发的安全性。而消费者维护消息指针,这样可以带来很多的功能,各个消费者可互不影响的消费。但内部也提供了如顺序执行,同步执行,等消费机制。通过取余的方式来访问数组的位置,且不需要考虑溢出的问题,因为long类型的自增,是短时间内无法一溢出。且其需要跟踪消费者的位置,来确定是否队列已满。
Sequence
最核心的组件,通过sequence达到多生产者互斥的访问,生产者与消费者之间的协调,以及消纳者之间的协调。其本质是一个递增的序号(计数器),线程间引用传递,CAS更新。线程安全。并且通过padding避免伪共享。
下面给出一个多消费者如何获取资源的源码。当生产者需要的资源超过了可用资源时抛异常。
//多生产者时获取n个生产位置
public long tryNext(int n) throws InsufficientCapacityException {
if(n < 1) {
throw new IllegalArgumentException("n must be > 0");
} else {
long current;
long next;
do {
current = this.cursor.get();
next = current + (long)n;
if(!this.hasAvailableCapacity(this.gatingSequences, n, current)) {//检测是否超过最慢的消费者
throw InsufficientCapacityException.INSTANCE;
}
} while(!this.cursor.compareAndSet(current, next));
return next;
}
}
private boolean hasAvailableCapacity(Sequence[] gatingSequences, int requiredCapacity, long cursorValue) {
long wrapPoint = cursorValue + (long)requiredCapacity - (long)this.bufferSize;
long cachedGatingSequence = this.gatingSequenceCache.get();
if(wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue) {
long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue);
this.gatingSequenceCache.set(minSequence);
if(wrapPoint > minSequence) {
return false;
}
}
return true;
}
SequenceBarrier
用来队列(RingBuffer)与消费者以及消费者与消费与消费者之间的依赖关系,也就是一个执行顺序的的定义。所以消费者必须 小于队列中的curosr(当前队列中队首的位置).SequenceBarrier会收集所依赖的组件的Sequencr.
WaitStrategy
当消费者等待在SequenceBarrier上时,有多种策略,在延迟和CPU资源的占用上各有不同。
-
BusySpinWaitStrategy : 自旋等待,类似Linux Kernel使用的自旋锁。低延迟但同时对CPU资源的占用也多。
-
BlockingWaitStrategy : 使用锁和条件变量。CPU资源的占用少,延迟大。
-
SleepingWaitStrategy : 在多次循环尝试不成功后,选择让出CPU,等待下次调度,多次调度后仍不成功,尝试前睡眠一个纳秒级别的时间再尝试。这种策略平衡了延迟和CPU资源占用,但延迟不均匀。
-
YieldingWaitStrategy : 在多次循环尝试不成功后,选择让出CPU,等待下次调。平衡了延迟和CPU资源占用,但延迟也比较均匀。
-
PhasedBackoffWaitStrategy : 上面多种策略的综合,CPU资源的占用少,延迟大。
BatchEvenProcessor
在Disruptor中,消费者是以EventProcessor的形式存在的。其中一类消费者是BatchEvenProcessor。每个BatchEvenProcessor有一个Sequence,来记录自己消费RingBuffer中消息的情况。所以,一个消息必然会被每一个BatchEvenProcessor消费。
WorkProcessor
另一类消费者是WorkProcessor。每个WorkProcessor也有一个Sequence,多个WorkProcessor还共享一个Sequence用于互斥的访问RingBuffer。一个消息被一个WorkProcessor消费,就不会被共享一个Sequence的其他WorkProcessor消费。这个被WorkProcessor共享的Sequence相当于尾指针。
WorkerPool
共享同一个Sequence的WorkProcessor可由一个WorkerPool管理,这时,共享的Sequence也由WorkerPool创建。
http://www.tuicool.com/articles/b2eUBn
http://tech.meituan.com/disruptor.html
http://ifeve.com/disruptor/
http://ziyue1987.github.io/pages/2013/09/22/disruptor-use-manual.html#onepublishertoonebatcheventprocessor
来源:oschina
链接:https://my.oschina.net/u/2834932/blog/806379