(轉)disruptor - Concurrent Programming Framework...

disruptor發佈了Java的2.0版本(.Net版本見這裏),disruptor是一個高性能的異步處理框架,或者能夠認爲是最快的消息框架(輕量的JMS),也能夠認爲是一個觀察者模式實現,或者事件-監聽模式的實現,直接稱disruptor模式。

disruptor最大特色是高性能,其LMAX架構能夠得到每秒6百萬訂單,用1微秒的延遲得到吞吐量爲100K+。

disruptor與傳統高性能模型是不一樣的,LMAX團隊經過測試發現熱門的Actor模型在高併發設計有瓶頸,disruptor的RingBuffer根據多核CPU的高速緩存設計特色進行了優化,讓每一個CPU運行一個線程,多個CPU就是多線程併發模式了,正如團隊所言:咱們想出一個更好,更快的線程之間共享數據的方式,不與世界分享將是自私的,不共享知識讓咱們看上去是死聰明。

傳統消息框架使用Queue隊列,如JDK LinkedList等數據結構實現,RingBuffer比Linked之類數據結構要快,由於沒有鎖,是CPU友好型的。另一個不一樣的地方是不會在清除RingBuffer中數據,只會覆蓋,這樣下降了垃圾回收機制啓動頻率。

使用案例代碼:
DisruptorWizard<MyEvent> dw = new DisruptorWizard<MyEvent>(MyEvent.FACTORY, 32, Executors.newCachedThreadPool());
		EventHandler<MyEvent> handler1 = new EventHandler<MyEvent>() {
			public void onEvent(MyEvent event, boolean endOfBatch) throws Exception {
				System.out.println("MyEvent=" + event.r); } }; EventHandler<MyEvent> handler2 = new EventHandler<MyEvent>() { public void onEvent(MyEvent event, boolean endOfBatch) throws Exception { System.out.println("MyEvent=" + event.getResult()); } }; dw.handleEventsWith(handler1); dw.after(handler1).handleEventsWith(handler2); RingBuffer ringBuffer = dw.start(); MyEvent event = (MyEvent) ringBuffer.nextEvent(); event.setValue(60); ringBuffer.publish(event);

或者:
SampleExecutor executor = new SampleExecutor();
		RingBuffer<MyEvent> ringBuffer = new RingBuffer<MyEvent>(MyEvent.FACTORY, 4, ClaimStrategy.Option.SINGLE_THREADED,
				WaitStrategy.Option.YIELDING);
		MyBatchHandler batchHandler = new MyBatchHandler();

		DependencyBarrier dependencyBarrier = ringBuffer.newDependencyBarrier();
		BatchEventProcessor<MyEvent> batchProcessorFizz = new BatchEventProcessor<MyEvent>(ringBuffer, dependencyBarrier, batchHandler);
		executor.execute(batchProcessorFizz);

		MyEvent event = ringBuffer.nextEvent();
		event.setValue(60);
		ringBuffer.publish(event);

Disruptor沒有像JDK的LinkedBlockQueue等那樣使用鎖,針對CPU高速緩存進行了優化。

原來咱們覺得多個線程同時寫一個類的字段會發生爭奪,這是多線程基本原理,因此使用了鎖機制,保證這個共用字段(資源)可以某個時刻只能一個線程寫,可是這樣作的壞處是:有可能發生死鎖。

好比1號線程前後訪問共享資源A和B;而2號線程前後訪問共享資源B和A,由於在資源A和資源B都有鎖,那麼1號在訪問資源A時,資源A上鎖了,準備訪問資源B,可是沒法訪問,由於與此同時;而2號線程在訪問資源B,資源B鎖着呢,正準備訪問資源A,發現資源A被1號線程鎖着呢,結果彼此無限等待彼此下去,死鎖相似邏輯上自指悖論。

因此,鎖是壞的,破壞性能,鎖是併發計算的大敵。

咱們回到隊列上,一把一個隊列有至少兩個線程:生產者和消費者,這就具有了資源爭奪的前提,這兩個線程通常彼此守在隊列的進出兩端,表面上好像沒有訪問共享資源,實際上隊列存在兩個共享資源:隊列大小或指針. 

除了共享資源寫操做上存在資源爭奪問題外,Disruptor的LMAX團隊發現Java或C#在多核CPU狀況下有僞共享問題
CPU會把數據從內存加載到高速緩存中 ,這樣能夠得到更好的性能,高速緩存默認大小是64 Byte爲一個區域,CPU機制限制只能一個CPU的一個線程訪問(寫)這個高速緩存區。

CPU在將主內存中數據加載到高速緩衝時,若是發現被加載的數據不足64字節,那麼就會加載多個數據,以填滿本身的64字節,悲催就發生了,偏偏otspot JVM中對象指針等大小都不會超過64字節,這樣一個高速緩衝中可能加載了兩個對象指針,一個CPU一個高速緩衝,雙核就是兩個CPU各自一個高速緩衝,那麼兩個高速緩衝中各有兩個對象指針,都是指向相同的兩個對象。

由於一個CPU只能訪問(寫)本身高速緩存區中數據,至關於給這個數據加鎖,那麼另一個CPU同時訪問本身高速緩衝中一樣數據時將會被鎖定不能訪問。

這就發生與鎖機制相似的性能陷進,Disruptor的解決辦法是填滿高速緩衝的64字節,不是對象指針等數據不夠64字節嗎?那麼加一些字節填滿64字節,這樣CPU將數據加載到高速緩衝時,就只能加載一個了,剛恰好啊。

因此,儘管兩個線程是在寫兩個不一樣的字段值,也會由於雙核CPU底層機制發生假裝的共享,並無真正共享,其實仍是排他性的獨享。

如今咱們大概知道RingBuffer是個什麼東東了:
1.ring buffer是一個大的數組.
2.RingBuffer裏全部指針都是Java longs (64字節) 不斷永遠向前計數,如後面圖,不斷在圓環中循環。
3.RingBuffer只有當前序列號,沒有終點序列號,其中數據不會被取出後消除,這樣以便實現從過去某個序列號到當前序列號的重放,這樣當消費者說沒有接受到生產者發送的消息,生產者還能夠再次發送,這點是一種原子性的「事務」機制。

鑑於Disruptor如此革命性的優勢,JdonFramework 6.4新版 採起Disruptor做爲其Domain Events實現機制。 便可以方便簡單享用Disruptor的新特色;又能根據本身的要求繼續深化使用Disruptor,最極致的狀況如運行的LMAX系統同樣,每秒處理6百萬個訂單。



相關文章
相關標籤/搜索