disruptor併發編程一:入門使用

disruptor併發編程一:入門使用

  • Martin Fowler寫了一篇LMAX架構的文章,在文章中他介紹了LMAX是一種新型零售金融交易平臺,它能夠以很低的延遲產生大量交易。這個系統是建立在JVM平臺上,其核心是一個業務邏輯處理器,它能夠在一個線程裏每秒處理6百萬訂單。業務邏輯處理器完全是運行在內存中,使`用事件源驅動方式。業務邏輯處理器的核心是Disruptor。
  • Disruptor它是一個開源的併發框架,並獲得2011 Duke’s 程序框架創新獎,能夠在無鎖的情況下實現網絡的Queue併發操作。
  • Disruptor是一個高性能的異步處理框架,或者可以認爲是最快的消息框架(輕量的JMS),也可以認爲是一個觀察者模式的實現,或者事件監聽模式的實現。

一、引入disruptor框架

目前disruptor已經更新到了3.x版本,比之前的2.x版本性能更加的優秀,提供更多的API使用方式,直接下載jar包,addTopath就可使用,用如下maven依賴也可:

<!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.2</version>
</dependency>

二、示例

步驟:
第一:定義一個Event類,是一個常規javabean。
第二:繼承disruptor工廠類,實現能產生Event實例的工廠,用於創建Event類實例對象。
第三:繼承disruptor處理器,實現一個監聽事件類,用於處理數據(Event類)
第四:應用層使用:實例化Disruptor實例,配置參數。然後我們對Disruptor實例綁定監聽事件類,接受並處理數據。
第五:在Disruptor中,真正存儲數據的核心叫做RingBuffer,我們通過Disruptor實例拿到它,然後把數據生產出來,把數據加入到RingBuffer的實例對象中,以便讓第四步綁定的監聽器處理數據。

代碼:
(1) StringEvent.java 純javabean

package com.ty.disruptor;

 public class StringEvent {	
	private Integer id;	
	private String value;	
	public Integer getId() {		
		return id;
	}	
	public void setId(Integer id) {		
		this.id = id;
	}	
	public String getValue() {		
		return value;
	}	
	public void setValue(String value) {		
		this.value = value;
	}	
	
	@Override
	public String toString() {		
		return "StringEvent [id=" + id + ", value=" + value + "]";
	}
}

(2) StringEventFactory.java 繼承disruptor工廠類,實現能產生Event實例的工廠

package com.ty.disruptor;
import com.lmax.disruptor.EventFactory;

public class StringEventFactory implements EventFactory<StringEvent> {	
	@Override
	public StringEvent newInstance() {	
		return new StringEvent();
	}

}

(3)繼承disruptor處理器,實現一個監聽事件類,用於處理數據(Event類)

package com.ty.disruptor;
import com.lmax.disruptor.EventHandler;

public class StringEventHandler implements EventHandler<StringEvent> {	
	
	@Override
	public void onEvent(StringEvent stringEvent, long sequence, boolean bool) throws Exception {
		System.out.println("StringEventHandler(消費者):  "  + stringEvent +", sequence= "+sequence+",bool="+bool);    
	}

}

(4) StringEventProducer.java 一個事件源,用來觸發disruptor事件

package com.ty.disruptor;
import java.nio.ByteBuffer;
import com.lmax.disruptor.RingBuffer;

public class StringEventProducer {     
	private final RingBuffer<StringEvent> ringBuffer;	
	public StringEventProducer(RingBuffer<StringEvent> ringBuffer) {	     
		this.ringBuffer = ringBuffer;
	}	 
	
	public void sendData(ByteBuffer byteBuffer) {  //ringBuffer就是用來存儲數據的,具體可以看disruptor源碼的數據結構,next就是獲取下一個空事件索引
		long sequence = ringBuffer.next();	     //通過索引獲取空事件 	
		try {			
			StringEvent stringEvent = ringBuffer.get(sequence);	//切換成讀模式
			byteBuffer.flip();			//從byteBuffer中讀取傳過來的值
			byte[] dst = new byte[byteBuffer.limit()];
			byteBuffer.get(dst, 0, dst.length);	//爲stringEvent賦值,填充數據
			stringEvent.setValue(new String(dst));
			stringEvent.setId((int) sequence);	//clear一下緩衝區
			byteBuffer.clear();
		} finally {			//發佈事件,爲確保安全,放入finally中,不會造成disruptor的混亂
			ringBuffer.publish(sequence);
		}
	} 
}

另外一種替代的寫法,disruptor3推薦的,這樣更靈活。
StringEventProducerWithTranslator.java

package com.ty.disruptor;
import java.nio.ByteBuffer;
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.RingBuffer;

/*
 * Translator可以看做一個事件初始化器
 */ 
 public class StringEventProducerWithTranslator {	
 private final RingBuffer<StringEvent> ringBuffer;	
	//填充數據
	public static final EventTranslatorOneArg<StringEvent, ByteBuffer> TRANSLATOR = new EventTranslatorOneArg<StringEvent, ByteBuffer>() {		
		@Override
		public void translateTo(StringEvent stringEvent, long sequence, ByteBuffer byteBuffer) { // 從byteBuffer中讀取傳過來的值
			byteBuffer.flip();			
			byte[] dst = new byte[byteBuffer.limit()];
			byteBuffer.get(dst, 0, dst.length);
			byteBuffer.clear();  //爲stringEvent賦值,填充數據
			stringEvent.setValue(new String(dst));
			stringEvent.setId((int) sequence);
		}
	};	

	public StringEventProducerWithTranslator( RingBuffer<StringEvent> ringBuffer) {		
		this.ringBuffer = ringBuffer;
	}	

	//發佈事件 
   	public void sendData(ByteBuffer byteBuffer) {
		ringBuffer.publishEvent(TRANSLATOR, byteBuffer);
	}
}

發送數據與填充數據兩個動作被Translator進行了分離。

(5) 應用層調用


public class AppMain {

	public static void main(String[] args) throws Exception {
		//創建一個線程池
		ExecutorService executorPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
		//創建Event工廠
		StringEventFactory factory = new StringEventFactory();
		/*
		 *	  創建Disruptor對象
		 *   eventFactory, 傳入實現了EventFactory藉口的工廠類
		 *   ringBufferSize, 用來存儲數據的, 值爲 2^n  
		 *   executor, 線程池 
		 *   producerType, 類型,可以是多個生產者,也可以是單個生產者 
		 *   waitStrategy, 使用什麼策略,消費者如何等待生產者放入disruptor中 :
				     BlockingWaitStrategy 是最低效的策略,但其對CPU的消耗最小並且在各種不同部署環境中能提供更加一致的性能表現
					 SleepingWaitStrategy 的性能表現跟BlockingWaitStrategy差不多,對CPU的消耗也類似,但其對生產者線程的影響最小,適合用於異步日誌類似的場景
					 YieldingWaitStrategy 的性能是最好的,適合用於低延遲的系統。在要求極高性能且事件處理線數小於CPU邏輯核心數的場景中,推薦使用此策略;例如,CPU開啓超線程的特性
		 */
		Disruptor<StringEvent> disruptor = new Disruptor<>(factory, (int)Math.pow(2, 20), executorPool, ProducerType.SINGLE, new YieldingWaitStrategy());
		//關聯處理器,也就是消費者,連接消費事件方法
		disruptor.handleEventsWith(new StringEventHandler());
		//啓動
		disruptor.start();
		//獲取RingBuffer,模擬生產者發佈消息
		RingBuffer<StringEvent> ringBuffer = disruptor.getRingBuffer();
		
		StringEventProducerWithTranslator producer = new StringEventProducerWithTranslator(ringBuffer);
		//StringEventProducer producer = new StringEventProducer(ringBuffer);
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		
		//閉鎖控制線程同步
		CountDownLatch countDownLatch = new CountDownLatch(1);
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i = 0 ; i < 10 ; i ++) {
					//下面是進行觸發事件並且發佈
					byteBuffer.put(new String("生產者發佈第"+ i +"條消息").getBytes());
					producer.sendData(byteBuffer);
					//模擬進行其他操作的耗時
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				countDownLatch.countDown();
			}
		},"Thread2").start();;
		//等待
		countDownLatch.await();
		disruptor.shutdown(); //關閉 disruptor,方法會堵塞,直至所有的事件都得到處理;
		executorPool.shutdown(); //關閉使用的線程池;如果需要的話,必須手動關閉, disruptor 在 shutdown 時不會自動關閉;   
	}
}

運行結果

StringEventHandler(消費者):  StringEvent [id=0, value=生產者發佈第0消息], sequence= 0,bool=true
StringEventHandler(消費者):  StringEvent [id=1, value=生產者發佈第1消息], sequence= 1,bool=true
StringEventHandler(消費者):  StringEvent [id=2, value=生產者發佈第2消息], sequence= 2,bool=true
StringEventHandler(消費者):  StringEvent [id=3, value=生產者發佈第3消息], sequence= 3,bool=true
StringEventHandler(消費者):  StringEvent [id=4, value=生產者發佈第4消息], sequence= 4,bool=true
StringEventHandler(消費者):  StringEvent [id=5, value=生產者發佈第5消息], sequence= 5,bool=true
StringEventHandler(消費者):  StringEvent [id=6, value=生產者發佈第6消息], sequence= 6,bool=true
StringEventHandler(消費者):  StringEvent [id=7, value=生產者發佈第7消息], sequence= 7,bool=true
StringEventHandler(消費者):  StringEvent [id=8, value=生產者發佈第8消息], sequence= 8,bool=true
StringEventHandler(消費者):  StringEvent [id=9, value=生產者發佈第9消息], sequence= 9,bool=true

每隔一秒事件源sendData(),消費者就開始消費數據,這裏實質上是一種觀察者模式,內部狀態改變會通知所有的消費者,有興趣的可以去了解下觀察者模式。

參考:

  1. https://www.imooc.com/article/68795
  2. http://ifeve.com/the-disruptor-lock-free-publishing/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章