SpringBoot?+?Disruptor實(shí)現(xiàn)特快高并發(fā)處理及使用Disruptor高速實(shí)現(xiàn)隊(duì)列的過程
1 前言
工作中遇到項(xiàng)目使用Disruptor做消息隊(duì)列,對!你沒看錯(cuò),不是Kafka也不是rabbitmq。Disruptor有個(gè)最大的優(yōu)點(diǎn)就是快,還有一點(diǎn)它是開源的哦,下面做個(gè)簡單的記錄。
2 Disruptor介紹
Disruptor 是英國外匯交易公司LMAX開發(fā)的一個(gè)高性能隊(duì)列,研發(fā)的初衷是解決內(nèi)存隊(duì)列的延遲問題(在性能測試中發(fā)現(xiàn)竟然與I/O操作處于同樣的數(shù)量級)。
基于 Disruptor 開發(fā)的系統(tǒng)單線程能支撐每秒 600 萬訂單,2010 年在 QCon 演講后,獲得了業(yè)界關(guān)注。
Disruptor是一個(gè)開源的Java框架,它被設(shè)計(jì)用于在生產(chǎn)者—消費(fèi)者(producer-consumer problem,簡稱PCP)問題上獲得盡量高的吞吐量(TPS)和盡量低的延遲。
從功能上來看,Disruptor 是實(shí)現(xiàn)了“隊(duì)列”的功能,而且是一個(gè)有界隊(duì)列。那么它的應(yīng)用場景自然就是“生產(chǎn)者-消費(fèi)者”模型的應(yīng)用場合了。
Disruptor是LMAX在線交易平臺的關(guān)鍵組成部分,LMAX平臺使用該框架對訂單處理速度能達(dá)到600萬TPS,除金融領(lǐng)域之外,其他一般的應(yīng)用中都可以用到Disruptor,它可以帶來顯著的性能提升。
其實(shí)Disruptor與其說是一個(gè)框架,不如說是一種設(shè)計(jì)思路,這個(gè)設(shè)計(jì)思路對于存在“并發(fā)、緩沖區(qū)、生產(chǎn)者—消費(fèi)者模型、事務(wù)處理”這些元素的程序來說,Disruptor提出了一種大幅提升性能(TPS)的方案。
Disruptor的github主頁:
3 Disruptor 的核心概念
先從了解 Disruptor 的核心概念開始,來了解它是如何運(yùn)作的。下面介紹的概念模型,既是領(lǐng)域?qū)ο?,也是映射到代碼實(shí)現(xiàn)上的核心對象。
3.1 Ring Buffer
如其名,環(huán)形的緩沖區(qū)。曾經(jīng) RingBuffer 是 Disruptor 中的最主要的對象,但從3.0版本開始,其職責(zé)被簡化為僅僅負(fù)責(zé)對通過 Disruptor 進(jìn)行交換的數(shù)據(jù)(事件)進(jìn)行存儲和更新。在一些更高級的應(yīng)用場景中,Ring Buffer 可以由用戶的自定義實(shí)現(xiàn)來完全替代。
3.2 Sequence Disruptor
通過順序遞增的序號來編號管理通過其進(jìn)行交換的數(shù)據(jù)(事件),對數(shù)據(jù)(事件)的處理過程總是沿著序號逐個(gè)遞增處理。一個(gè) Sequence 用于跟蹤標(biāo)識某個(gè)特定的事件處理者( RingBuffer/Consumer )的處理進(jìn)度。
雖然一個(gè) AtomicLong 也可以用于標(biāo)識進(jìn)度,但定義 Sequence 來負(fù)責(zé)該問題還有另一個(gè)目的,那就是防止不同的 Sequence 之間的CPU緩存?zhèn)喂蚕?Flase Sharing)問題。
注:這是 Disruptor 實(shí)現(xiàn)高性能的關(guān)鍵點(diǎn)之一,網(wǎng)上關(guān)于偽共享問題的介紹已經(jīng)汗牛充棟,在此不再贅述。
3.3 Sequencer
Sequencer 是 Disruptor 的真正核心。此接口有兩個(gè)實(shí)現(xiàn)類 SingleProducerSequencer、MultiProducerSequencer ,它們定義在生產(chǎn)者和消費(fèi)者之間快速、正確地傳遞數(shù)據(jù)的并發(fā)算法。
3.4 Sequence Barrier
用于保持對RingBuffer的 main published Sequence 和Consumer依賴的其它Consumer的 Sequence 的引用。Sequence Barrier 還定義了決定 Consumer 是否還有可處理的事件的邏輯。
3.5 Wait Strategy
定義 Consumer 如何進(jìn)行等待下一個(gè)事件的策略。(注:Disruptor 定義了多種不同的策略,針對不同的場景,提供了不一樣的性能表現(xiàn))
3.6 Event
在 Disruptor 的語義中,生產(chǎn)者和消費(fèi)者之間進(jìn)行交換的數(shù)據(jù)被稱為事件(Event)。它不是一個(gè)被 Disruptor 定義的特定類型,而是由 Disruptor 的使用者定義并指定。
3.7 EventProcessor
EventProcessor 持有特定消費(fèi)者(Consumer)的 Sequence,并提供用于調(diào)用事件處理實(shí)現(xiàn)的事件循環(huán)(Event Loop)。
3.8 EventHandler
Disruptor 定義的事件處理接口,由用戶實(shí)現(xiàn),用于處理事件,是 Consumer 的真正實(shí)現(xiàn)。
3.9 Producer
即生產(chǎn)者,只是泛指調(diào)用 Disruptor 發(fā)布事件的用戶代碼,Disruptor 沒有定義特定接口或類型。
4 案例-demo
通過下面8個(gè)步驟,你就能將Disruptor Get回家啦:
4.1 添加pom.xml依賴
<dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.4</version> </dependency>
4.2 消息體Model
/** * 消息體 */ @Data public class MessageModel { private String message; }
4.3 構(gòu)造EventFactory
public class HelloEventFactory implements EventFactory<MessageModel> { @Override public MessageModel newInstance() { return new MessageModel(); } }
4.4 構(gòu)造EventHandler-消費(fèi)者
@Slf4j public class HelloEventHandler implements EventHandler<MessageModel> { @Override public void onEvent(MessageModel event, long sequence, boolean endOfBatch) { try { //這里停止1000ms是為了確定消費(fèi)消息是異步的 Thread.sleep(1000); log.info("消費(fèi)者處理消息開始"); if (event != null) { log.info("消費(fèi)者消費(fèi)的信息是:{}",event); } } catch (Exception e) { log.info("消費(fèi)者處理消息失敗"); } log.info("消費(fèi)者處理消息結(jié)束"); } }
4.5 構(gòu)造BeanManager
/** * 獲取實(shí)例化對象 */ @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); } }
4.6 構(gòu)造MQManager
@Configuration public class MQManager { @Bean("messageModel") public RingBuffer<MessageModel> messageModelRingBuffer() { //定義用于事件處理的線程池, Disruptor通過java.util.concurrent.ExecutorSerivce提供的線程來觸發(fā)consumer的事件處理 ExecutorService executor = Executors.newFixedThreadPool(2); //指定事件工廠 HelloEventFactory factory = new HelloEventFactory(); //指定ringbuffer字節(jié)大小,必須為2的N次方(能將求模運(yùn)算轉(zhuǎn)為位運(yùn)算提高效率),否則將影響效率 int bufferSize = 1024 * 256; //單線程模式,獲取額外的性能 Disruptor<MessageModel> disruptor = new Disruptor<>(factory, bufferSize, executor, ProducerType.SINGLE, new BlockingWaitStrategy()); //設(shè)置事件業(yè)務(wù)處理器---消費(fèi)者 disruptor.handleEventsWith(new HelloEventHandler()); // 啟動disruptor線程 disruptor.start(); //獲取ringbuffer環(huán),用于接取生產(chǎn)者生產(chǎn)的事件 RingBuffer<MessageModel> ringBuffer = disruptor.getRingBuffer(); return ringBuffer; } }
4.7 構(gòu)造Mqservice和實(shí)現(xiàn)類-生產(chǎn)者
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); //獲取下一個(gè)Event槽的下標(biāo) long sequence = messageModelRingBuffer.next(); try { //給Event填充數(shù)據(jù) MessageModel event = messageModelRingBuffer.get(sequence); event.setMessage(message); log.info("往消息隊(duì)列中添加消息:{}", event); } catch (Exception e) { log.error("failed to add event to messageModelRingBuffer for : e = {},{}",e,e.getMessage()); } finally { //發(fā)布Event,激活觀察者去消費(fèi),將sequence傳遞給改消費(fèi)者 //注意最后的publish方法必須放在finally中以確保必須得到調(diào)用;如果某個(gè)請求的sequence未被提交將會堵塞后續(xù)的發(fā)布操作或者其他的producer messageModelRingBuffer.publish(sequence); } } }
4.8 構(gòu)造測試類及方法
@Slf4j @RunWith(SpringRunner.class) @SpringBootTest(classes = DemoApplication.class) public class DemoApplicationTests { @Autowired private DisruptorMqService disruptorMqService; /** * 項(xiàng)目內(nèi)部使用Disruptor做消息隊(duì)列 * @throws Exception */ @Test public void sayHelloMqTest() throws Exception{ disruptorMqService.sayHelloMq("消息到了,Hello world!"); log.info("消息隊(duì)列已發(fā)送完畢"); //這里停止2000ms是為了確定是處理消息是異步的 Thread.sleep(2000); } }
4.9 測試運(yùn)行結(jié)果
2023-04-05 14:31:18.543 INFO 7274 --- [ main] c.e.u.d.d.s.Impl.DisruptorMqServiceImpl : record the message: 消息到了,Hello world!
2023-04-05 14:31:18.545 INFO 7274 --- [ main] c.e.u.d.d.s.Impl.DisruptorMqServiceImpl : 往消息隊(duì)列中添加消息:MessageModel(message=消息到了,Hello world!)
2023-04-05 14:31:18.545 INFO 7274 --- [ main] c.e.utils.demo.DemoApplicationTests : 消息隊(duì)列已發(fā)送完畢
2023-04-05 14:31:19.547 INFO 7274 --- [pool-1-thread-1] c.e.u.d.disrupMq.mq.HelloEventHandler : 消費(fèi)者處理消息開始
2023-04-05 14:31:19.547 INFO 7274 --- [pool-1-thread-1] c.e.u.d.disrupMq.mq.HelloEventHandler : 消費(fèi)者消費(fèi)的信息是:MessageModel(message=消息到了,Hello world!)
2023-04-05 14:31:19.547 INFO 7274 --- [pool-1-thread-1] c.e.u.d.disrupMq.mq.HelloEventHandler : 消費(fèi)者處理消息結(jié)束
5 總結(jié)
其實(shí) 生成者 -> 消費(fèi)者 模式是很常見的,通過一些消息隊(duì)列也可以輕松做到上述的效果。不同的地方在于,Disruptor 是在內(nèi)存中以隊(duì)列的方式去實(shí)現(xiàn)的,而且是無鎖的。這也是 Disruptor 為什么高效的原因。
到此這篇關(guān)于SpringBoot + Disruptor 實(shí)現(xiàn)特快高并發(fā)處理,使用Disruptor高速實(shí)現(xiàn)隊(duì)列的文章就介紹到這了,更多相關(guān)SpringBoot Disruptor 高并發(fā)處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
tk.Mybatis 插入數(shù)據(jù)獲取Id問題
本文主要介紹了tk.Mybatis 插入數(shù)據(jù)獲取Id問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12Spring中實(shí)現(xiàn)的三種異步流式接口方法
在現(xiàn)代Web開發(fā)中,接口超時(shí)是一個(gè)常見的問題,尤其是在處理耗時(shí)操作時(shí),傳統(tǒng)的同步接口在處理長時(shí)間任務(wù)時(shí)會阻塞請求線程,從而影響系統(tǒng)的響應(yīng)能力,本文將詳細(xì)講解Spring中實(shí)現(xiàn)的三種異步流式接口方法,需要的朋友可以參考下2024-10-10通過反射實(shí)現(xiàn)Java下的委托機(jī)制代碼詳解
這篇文章主要介紹了通過反射實(shí)現(xiàn)Java下的委托機(jī)制代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12Spring Cache + Caffeine的整合與使用示例詳解
對于一些項(xiàng)目里需要對數(shù)據(jù)庫里的某些數(shù)據(jù)一直重復(fù)請求的,且這些數(shù)據(jù)基本是固定的,在這種情況下,可以借助簡單使用本地緩存來緩存這些數(shù)據(jù),本文介紹一下Spring Cache和Caffeine的使用,感興趣的朋友一起看看吧2023-12-12解決Android Studio安裝后運(yùn)行出錯(cuò)dose not...和Internal error...
這篇文章主要介紹了解決Android Studio安裝后運(yùn)行出錯(cuò)dose not...和Internal error...的相關(guān)資料,需要的朋友可以參考下2017-03-03SpringBoot yaml中的數(shù)組類型取值方式
這篇文章主要介紹了SpringBoot yaml中的數(shù)組類型取值方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09spring boot微服務(wù)自定義starter原理詳解
這篇文章主要介紹了spring boot微服務(wù)自定義starter原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12