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(),消費者就開始消費數據,這裏實質上是一種觀察者模式,內部狀態改變會通知所有的消費者,有興趣的可以去了解下觀察者模式。
參考: