01、背景 工作中遇到項目使用Disruptor做消息隊列,對你沒看錯,不是Kafka,也不是rabbitmq;Disruptor有個最大的優點就是快,還有一點它是開源的哦,下麵做個簡單的記錄. 02、Disruptor介紹 Disruptor 是英國外匯交易公司LMAX開發的一個高性能隊列,研發的 ...
01、背景
工作中遇到項目使用Disruptor做消息隊列,對你沒看錯,不是Kafka,也不是rabbitmq;Disruptor有個最大的優點就是快,還有一點它是開源的哦,下麵做個簡單的記錄.
02、Disruptor介紹
- Disruptor 是英國外匯交易公司LMAX開發的一個高性能隊列,研發的初衷是解決記憶體隊列的延遲問題(在性能測試中發現竟然與I/O操作處於同樣的數量級)。基於 Disruptor 開發的系統單線程能支撐每秒 600 萬訂單,2010 年在 QCon 演講後,獲得了業界關註。
- Disruptor是一個開源的Java框架,它被設計用於在生產者—消費者(producer-consumer problem,簡稱PCP)問題上獲得儘量高的吞吐量(TPS)和儘量低的延遲。
- 從功能上來看,Disruptor 是實現了“隊列”的功能,而且是一個有界隊列。那麼它的應用場景自然就是“生產者-消費者”模型的應用場合了。
- Disruptor是LMAX線上交易平臺的關鍵組成部分,LMAX平臺使用該框架對訂單處理速度能達到600萬TPS,除金融領域之外,其他一般的應用中都可以用到Disruptor,它可以帶來顯著的性能提升。
- 其實Disruptor與其說是一個框架,不如說是一種設計思路,這個設計思路對於存在“併發、緩衝區、生產者—消費者模型、事務處理”這些元素的程式來說,Disruptor提出了一種大幅提升性能(TPS)的方案。
- Disruptor的github主頁:https://github.com/LMAX-Exchange/disruptor
03、Disruptor 的核心概念
先從瞭解 Disruptor 的核心概念開始,來瞭解它是如何運作的。下麵介紹的概念模型,既是領域對象,也是映射到代碼實現上的核心對象。
04、Ring Buffer
如其名,環形的緩衝區。曾經 RingBuffer 是 Disruptor 中的最主要的對象,但從3.0版本開始,其職責被簡化為僅僅負責對通過 Disruptor 進行交換的數據(事件)進行存儲和更新。在一些更高級的應用場景中,Ring Buffer 可以由用戶的自定義實現來完全替代。
05、Sequence Disruptor
通過順序遞增的序號來編號管理通過其進行交換的數據(事件),對數據(事件)的處理過程總是沿著序號逐個遞增處理。一個 Sequence 用於跟蹤標識某個特定的事件處理者( RingBuffer/Consumer )的處理進度。
雖然一個 AtomicLong 也可以用於標識進度,但定義 Sequence 來負責該問題還有另一個目的,那就是防止不同的 Sequence 之間的CPU緩存偽共用(Flase Sharing)問題。(註:這是 Disruptor 實現高性能的關鍵點之一,網上關於偽共用問題的介紹已經汗牛充棟,在此不再贅述)。
06、Sequencer
Sequencer 是 Disruptor 的真正核心。此介面有兩個實現類 SingleProducerSequencer、MultiProducerSequencer ,它們定義在生產者和消費者之間快速、正確地傳遞數據的併發演算法。
07、Sequence Barrier
用於保持對RingBuffer的 main published Sequence 和Consumer依賴的其它Consumer的 Sequence 的引用。Sequence Barrier 還定義了決定 Consumer 是否還有可處理的事件的邏輯。
08、Wait Strategy
定義 Consumer 如何進行等待下一個事件的策略。(註:Disruptor 定義了多種不同的策略,針對不同的場景,提供了不一樣的性能表現)
09、Event
在 Disruptor 的語義中,生產者和消費者之間進行交換的數據被稱為事件(Event)。它不是一個被 Disruptor 定義的特定類型,而是由 Disruptor 的使用者定義並指定。
10、EventProcessor
EventProcessor 持有特定消費者(Consumer)的 Sequence,並提供用於調用事件處理實現的事件迴圈(Event Loop)。
11、EventHandler
Disruptor 定義的事件處理介面,由用戶實現,用於處理事件,是 Consumer 的真正實現。
12、Producer
即生產者,只是泛指調用 Disruptor 發佈事件的用戶代碼,Disruptor 沒有定義特定介面或類型。
13、案例-demo
推薦一個 Spring Boot 基礎教程及實戰示例:https://github.com/javastacks/spring-boot-best-practice
通過下麵8個步驟,你就能將Disruptor Get回家啦:
1、添加pom.xml依賴
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
2、消息體Model
/**
* 消息體
*/
@Data
public class MessageModel {
private String message;
}
3、構造EventFactory
public class HelloEventFactory implements EventFactory<MessageModel> {
@Override
public MessageModel newInstance() {
return new MessageModel();
}
}
4、構造EventHandler-消費者
@Slf4j
public class HelloEventHandler implements EventHandler<MessageModel> {
@Override
public void onEvent(MessageModel event, long sequence, boolean endOfBatch) {
try {
//這裡停止1000ms是為了確定消費消息是非同步的
Thread.sleep(1000);
log.info("消費者處理消息開始");
if (event != null) {
log.info("消費者消費的信息是:{}",event);
}
} catch (Exception e) {
log.info("消費者處理消息失敗");
}
log.info("消費者處理消息結束");
}
}
5、構造BeanManager
/**
* 獲取實例化對象
*/
@Component
public class BeanManager implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() { return applicationContext; }
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
6、構造MQManager
@Configuration
public class MQManager {
@Bean("messageModel")
public RingBuffer<MessageModel> messageModelRingBuffer() {
//定義用於事件處理的線程池, Disruptor通過java.util.concurrent.ExecutorSerivce提供的線程來觸發consumer的事件處理
ExecutorService executor = Executors.newFixedThreadPool(2);
//指定事件工廠
HelloEventFactory factory = new HelloEventFactory();
//指定ringbuffer位元組大小,必須為2的N次方(能將求模運算轉為位運算提高效率),否則將影響效率
int bufferSize = 1024 * 256;
//單線程模式,獲取額外的性能
Disruptor<MessageModel> disruptor = new Disruptor<>(factory, bufferSize, executor,
ProducerType.SINGLE, new BlockingWaitStrategy());
//設置事件業務處理器---消費者
disruptor.handleEventsWith(new HelloEventHandler());
// 啟動disruptor線程
disruptor.start();
//獲取ringbuffer環,用於接取生產者生產的事件
RingBuffer<MessageModel> ringBuffer = disruptor.getRingBuffer();
return ringBuffer;
}
7、構造Mqservice和實現類-生產者
public interface DisruptorMqService {
/**
* 消息
* @param message
*/
void sayHelloMq(String message);
}
@Slf4j
@Component
@Service
public class DisruptorMqServiceImpl implements DisruptorMqService {
@Autowired
private RingBuffer<MessageModel> messageModelRingBuffer;
@Override
public void sayHelloMq(String message) {
log.info("record the message: {}",message);
//獲取下一個Event槽的下標
long sequence = messageModelRingBuffer.next();
try {
//給Event填充數據
MessageModel event = messageModelRingBuffer.get(sequence);
event.setMessage(message);
log.info("往消息隊列中添加消息:{}", event);
} catch (Exception e) {
log.error("failed to add event to messageModelRingBuffer for : e = {},{}",e,e.getMessage());
} finally {
//發佈Event,激活觀察者去消費,將sequence傳遞給改消費者
//註意最後的publish方法必須放在finally中以確保必須得到調用;如果某個請求的sequence未被提交將會堵塞後續的發佈操作或者其他的producer
messageModelRingBuffer.publish(sequence);
}
}
}
8、構造測試類及方法
Spring Boot 基礎就不介紹了,推薦下這個實戰教程:
https://github.com/javastacks/spring-boot-best-practice
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DemoApplicationTests {
@Autowired
private DisruptorMqService disruptorMqService;
/**
* 項目內部使用Disruptor做消息隊列
* @throws Exception
*/
@Test
public void sayHelloMqTest() throws Exception{
disruptorMqService.sayHelloMq("消息到了,Hello world!");
log.info("消息隊列已發送完畢");
//這裡停止2000ms是為了確定是處理消息是非同步的
Thread.sleep(2000);
}
}
測試運行結果
2020-04-05 14:31:18.543 INFO 7274 --- [ main] c.e.u.d.d.s.Impl.DisruptorMqServiceImpl : record the message: 消息到了,Hello world!
2020-04-05 14:31:18.545 INFO 7274 --- [ main] c.e.u.d.d.s.Impl.DisruptorMqServiceImpl : 往消息隊列中添加消息:MessageModel(message=消息到了,Hello world!)
2020-04-05 14:31:18.545 INFO 7274 --- [ main] c.e.utils.demo.DemoApplicationTests : 消息隊列已發送完畢
2020-04-05 14:31:19.547 INFO 7274 --- [pool-1-thread-1] c.e.u.d.disrupMq.mq.HelloEventHandler : 消費者處理消息開始
2020-04-05 14:31:19.547 INFO 7274 --- [pool-1-thread-1] c.e.u.d.disrupMq.mq.HelloEventHandler : 消費者消費的信息是:MessageModel(message=消息到了,Hello world!)
2020-04-05 14:31:19.547 INFO 7274 --- [pool-1-thread-1] c.e.u.d.disrupMq.mq.HelloEventHandler : 消費者處理消息結束
14、總結
其實 生成者 -> 消費者 模式是很常見的,通過一些消息隊列也可以輕鬆做到上述的效果。不同的地方在於,Disruptor 是在記憶體中以隊列的方式去實現的,而且是無鎖的。
這也是 Disruptor 為什麼高效的原因。
版權聲明:本文為CSDN博主「不二天次」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。原文鏈接:https://blog.csdn.net/buertianci/article/details/105327031
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
覺得不錯,別忘了隨手點贊+轉發哦!