爲什麼用Disruptor?
傳統阻塞的隊列使用鎖保證線程安全,而鎖通過操作系統內核上下文切換實現,會暫停線程去等待鎖,直到鎖釋放。
執行這樣的上下文切換,會丟失之前保存的數據和指令。由於消費者和生產者之間的速度差異,隊列總是接近滿或者空的狀態,這種狀態會導致高水平的寫入爭用。
畢竟鎖的技術會導致性能變差,而且還有可能會造成死鎖。
什麼是Disruptor?
Disruptor的流行起源於Martin Fowloer在自己的網站上寫了一篇LMAX架構的文章,文章中介紹了LMAX是一種新型零售金融交易平臺,能夠以很低的延遲產生大量的交易,這個系統是建立在JVM平臺之上的,其核心是一個業務邏輯處理器,一個線程裏每秒處理六百萬訂單。業務邏輯處理器完全是允許在內存中,使用事件源驅動方式,而這個處理器的核心就是Disruptor。
Disruptor是一個多線程併發框架,能夠在無鎖的情況下實現網絡的Queue併發操作,是一個高性能的異步處理框架,也可以認爲是一個觀察者或事件監聽模式的實現。
Disruptor根本不用鎖,取而代之的是,在需要確保操作是線程安全的(特別是,在多生產者場景下,更新下一個可用的序列號)地方,使用CAS(Compare And Swap/Set)操作。這是一個CPU級別的指令,工作方式有點像樂觀鎖,CPU去更新一個值,但如果想改的值不再是原來的值,操作就失敗,因爲很明顯,有其他操作先改變了這個值。
CAS操作比鎖消耗的資源少很多,主要不涉及操作系統,直接在CPU上操作。但並非是沒有才加的,只是比使用鎖耗時少,比不需要考慮競爭的單線程耗時多。
在Disruptor中,多線程那個使用了AtomicLong(Java提供的CAS操作),而單線程使用long,沒有鎖也沒有CAS,意味着單線程版本非常快,不會產生序號上的衝突。在整個框架中,只有一個地方出現多線程競爭修改同一個變量值,如果只有一個生產者,那麼系統中國的每一個序號由一個線程寫入,這意味着沒有競爭,也不需要鎖、甚至不需要CAS。如果存在多個生產者,唯一會被多線程寫入的序號就是ClaimStrate對象裏的那個。
核心組件
RingBuffer:被看做Disruptor最主要的組件,在3.X後,RingBuffer僅僅負責存儲和更新在Disruptor中流通的數據,對一些特殊使用場景能夠被其他數據結構完全替代。
Sequence:Disruptor使用Sequence來表示一個特殊組織間處理的序號,每個消費者(EventProcessor)都維持着一個Sequence。大部分的併發代碼都依賴這些Sequence值運算,因此Sequence支持多種當前爲AtomicLong類的特性。
Sequencer:這是Disruptor真正的核心,實現了這個接口的兩種生產者(單生產者和多生產者)均實現了所有的併發算法,爲了在生產者和消費者之間進行準確的數據傳遞。
SequenceBarrirer:由Sequence生產,包含已經發布的Sequence的引用,這些Sequence源於Sequencer和一些獨立的消費者的Sequence,包含了決定是否有供消費者來消費的Event的邏輯。
WaitStrategy:決定一個消費者將如何等待生產者將Event置入Disruptor中。
Event:從生產者到消費者的過程中所處理的數據單元。Disruptor中沒有代表標識Event,完全由用戶定義的。
EventProcessor:主要事件循環,處理Disruptor中的Event,並且擁有消費者的Sequence。有一個實現類就是BatchEventProcessor,包含了EventLoop有效的實現,並且將回調到一個EventHandler接口的實現對象。
EventHandler:用戶實現,代表了Disruptor中的一個消費者的接口。
Producer:用戶實現,調用RingBuffer來插入Event(事件),在Disruptor中沒有相應的實現代碼。
WorkProcessor:確保每個Sequence只被一個Processor消費,在同一個WorkPool中的處理多個WorkProcessor不會消費同樣的Sequence。
WorkerPool:一個WorkProcessor池,其中的WorkerProcess將消費Sequence,所以任務可以在實現WorkHandler接口的Worker之間移交。
LifecycleAware:但BatchEventProcessor啓動和停止時,與實現這個接口用於接收通知。
import java.nio.ByteBuffer; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
import com.lmax.disruptor.*; import com.lmax.disruptor.dsl.*;
public class D01HelloWorld { public static void main(String[] args) { //線程池 ExecutorService pool = Executors.newCachedThreadPool(); //工廠,生成數據 EventFactory<Data> factory = new DataFactory(); //buffSize,也就是RingBuffer,2的N次方是最好的 int ringBufferSize = 1024 * 1024;
/* * BlockingWaitStrategy:最低效的策略,但對CPU的消耗最小,並且在各種不通過部署環境中能提供更加一致的性能。 * SleepingWaitStreategy:性能表現跟BlockingWaitStrategy差不多,對CPU的消耗也類似,但其對生產者線程影響最小,適合異步日誌類似的場景。 * YieldingWaitStrategy:性能最好,適合用於低延遲的系統,在要求極高性能而且事件處理線程數小於CPU邏輯核心數的場景中,例如CPU開啓超特性。 */
//創建Disruptor Disruptor<Data> disruptor = new Disruptor<>(factory, ringBufferSize, pool, ProducerType.SINGLE,new YieldingWaitStrategy());
//處理事件的消費者 disruptor.handleEventsWith(new DataConsumer());
//啓動 disruptor.start();
//發佈事件 RingBuffer<Data> ringBuffer = disruptor.getRingBuffer();
//生產者 DataProducer dataProducer = new DataProducer(ringBuffer); // DataProducer2 dataProducer = new DataProducer2(ringBuffer);
//預先分配空間 ByteBuffer byteBuffer = ByteBuffer.allocate(10); for (byte i = 0; i < 100; i++) { byteBuffer.put(0,i); //生產者發佈數據 dataProducer.publish(byteBuffer); }
disruptor.shutdown(); //所有事件處理完畢才關閉 pool.shutdown();
//1、啓動項目 //2、註釋DataProducer dataProducer,打開新的DataProducer2 dataProducer,查看簡化的發佈 } } /** * 傳輸的數據 */ class Data { private String value;
public String getValue() { return value; }
public void setValue(String value) { this.value = value; } } /** * 生產者 */ class DataProducer { //環(可以當成隊列),存儲數據的地方 private RingBuffer<Data> ringBuffer;
public DataProducer(RingBuffer<Data> ringBuffer) { super(); this.ringBuffer = ringBuffer; }
/** * 調用一次就發佈一次數據,會用事件的形式傳遞給消費者。 * 用一個簡單隊列來發布事件(數據)的時候會涉及很多細節,這是因爲事件(數據)對象需要預先創建好。 * 發佈事件(數據)至少要兩個操作: * 1、獲取下一個事件(數據)槽併發布事件。 * 如果使用RingBuffer.next()獲取事件(數據)槽,那麼一定要發佈對應的事件。 * 如果不能發佈事件(數據),那麼就會引起Disruptor狀態的混亂。 * 尤其在多個事件生產者的情況下會閥值事件消費者失速,從而不得不重啓應用才能恢復。 */ public void publish(ByteBuffer byteBuffer) { //可以把RingBuffer看成一個隊列。 long nextIndex = ringBuffer.next();
try { //根據索引獲取都數據 Data data = ringBuffer.get(nextIndex); data.setValue("生產者生產的數據:" + byteBuffer.get(0)); } finally { //發佈事件(數據)給消費者去消費 //最好是包含在finally中,確保必須得到調用,如果某個請求的nextIndex未被提交,將會堵塞,後續的發佈操作或者其他的producer ringBuffer.publish(nextIndex); } } } /** * 消費者:也就是事件處理器 */ class DataConsumer implements EventHandler<Data> { @Override public void onEvent(Data data, long l, boolean arg2) throws Exception { System.out.println("消費到:" + data.getValue() + " - " + l); } } /** * 讓Disruptor創建事件,同時還聲明瞭一個Factory來實例化事件(數據)對象填充到RingBuffer */ class DataFactory implements EventFactory<Data> { @Override public Data newInstance() { return new Data(); } } /** * 簡化發佈:3.x中提供Lambda表達式API,可以把一些複雜的操作放在RingBuffer, * 所以3.x以後的版本最好用Event Publisher或者Event Translator來發布 */ class DataProducer2 { private static final EventTranslatorOneArg<Data, ByteBuffer> TRANSLATOR = new EventTranslatorOneArg<Data, ByteBuffer>() {
@Override public void translateTo(Data data, long index, ByteBuffer byteBuffer) { data.setValue(byteBuffer.get(0) + ""); } };
private RingBuffer<Data> ringBuffer;
public DataProducer2(RingBuffer<Data> ringBuffer) { super(); this.ringBuffer = ringBuffer; }
/** * 發佈數據 */ public void publish(ByteBuffer byteBuffer) { ringBuffer.publishEvent(TRANSLATOR,byteBuffer); } } |
RingBuffer
RingBuffer就是一個環,每個槽都有一個序號,序號指向數組中下一個可用的元素,隨着不斷的填充這個Buffer,這個序號會一直增長,直到繞過這個環。
要找到數組中的當前序號指向的元素,可通過取模操作,所以這個槽的個數是2的N次方更有利於基於二進制的計算。
環是沒有尾指針的,維護了一個指向下一個可用位置的序號,最初的原因就是想要提供可靠的消息傳遞。
環和隊列的區別:不刪除環中的數據,也就是說數據一直存在環中,知道新的數據覆蓋,這也是不需要尾指針的原因。因爲是數組,所以比鏈表快,而且有一個容易預測的訪問模式,這對CPU緩存友好,在硬件級別上,數組中的元素是會被預加載的,因此在RingBuffer中,CPU不需要時不時去主內存在加載數組中的下一個元素。環也可以爲數組預先分配內存,使得數組對象一直存在,這意味着不需要大量的數據用於垃圾回收,不像鏈表那樣,需要尾每一個添加到其上面的對象創造節點對象對應的。當刪除節點時,需要執行相應的內存清理操作。
爲什麼這麼快:一是Disruptor通過將基本對象填充榮譽基本類型變量來充滿整個緩存行,就是一個緩存行8個變量,預設7個變量,然後保存一個唯一變量,這樣就不會出現相同的變量。二是無鎖隊列的實現,對於傳統併發隊列,至少要維護兩個指針,頭指針和尾指針。在併發訪問修改時,頭指針和尾指針的維護不可避免的應用了鎖,Disruptor由於是環狀隊列,對於Produce而言只有頭指針而且鎖是樂觀鎖,在標準的Disruptor應用中,只有一個生產者,避免了頭指針鎖的爭用,這也是爲什麼理解Disruptor爲無鎖隊列。
/** * 直接使用RingBuffer發佈,簡化操作 */ public class D02RingBuffer {
public static void main(String[] args) throws InterruptedException, ExecutionException {
D02RingBuffer d02RingBuffer = new D02RingBuffer(); d02RingBuffer.main1(); // d02RingBuffer.main2();
}
/* * 創建單個生產者的RingBuffer * 參數1:產生數據,填充到RingBuffer * 參數2:必須是2的指數倍,目的是爲了將取模運算轉爲&運算提高效率 * 參數3:等待策略 */ RingBuffer<RData> ringBuffer = RingBuffer.createSingleProducer(new EventFactory<RData>() { @Override public RData newInstance() { return new RData(); }
}, 1024);
//線程池 ExecutorService pool = Executors.newFixedThreadPool(5);
//創建SequenceBarrier,決定是否有供消費者來消費的Event的邏輯 SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();
/** * 寫法1 */ public void main1() throws InterruptedException, ExecutionException {
//創建消息處理器,指定消費者,單個消費者 BatchEventProcessor<RData> batchEventProcessor = new BatchEventProcessor<RData>(ringBuffer, sequenceBarrier, new RConsumer());
//把消費者的位置信息引入注入到生產者,讓生產者可以根據消費者的情況決定生產的速度,避免一個快、一個慢,如果只有一個消費者的情況可以省略 ringBuffer.addGatingSequences(batchEventProcessor.getSequence());
//把消息處理器給線程池 pool.submit(batchEventProcessor);
//如果有多個消費者則重複執行BatchEventProcessor、ringBuffer.addGatingSequences、pool.submit這三行代碼
Future<Void> future = pool.submit(new Callable<Void>() { @Override public Void call() throws Exception { //發佈數據給消費者消費 publish(); return null; } });
future.get();//等待生產者結束 Thread.sleep(1000); //等待處理結束 batchEventProcessor.halt();//通知事件(或者說消息)處理器,可用結束了 pool.shutdown();//關閉線程池 }
/** * 寫法2 * @throws InterruptedException */ public void main2() throws InterruptedException { WorkHandler<RData> workHandler = new RConsumer();
//配置都是類似的 WorkerPool<RData> workerPool = new WorkerPool<RData>(ringBuffer, sequenceBarrier, new IgnoreExceptionHandler(),workHandler); workerPool.start(pool);
//發佈數據給消費者消費 publish();
Thread.sleep(1000); //等待處理結束 workerPool.halt();//通知事件(或者說消息)處理器,可用結束了 pool.shutdown();//關閉線程池 }
/** * 發佈 */ public void publish() { for (int i = 0; i < 10; i++) { long nextSeq = ringBuffer.next(); //佔坑,ringBuffer一個可用區塊 RData rData = ringBuffer.get(nextSeq);//給這個位置放入數據 rData.setAge(i); rData.setName("Test " + i); ringBuffer.publish(nextSeq); } } } class RData { private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; } } /** * 消費者,實現那個都是可以的 * @author suzhiwei */ class RConsumer implements EventHandler<RData>,WorkHandler<RData> {
@Override public void onEvent(RData data, long arg1, boolean arg2) throws Exception { onEvent(data); }
@Override public void onEvent(RData data) throws Exception { // data.setAge(new Random().nextInt(),); System.out.println(data.getName() + " - " + data.getAge()); } } |