??說到隊列抹竹,大家都很熟悉,像生活中不管是吃飯還是買東西基本上都會遇到排隊父泳,先排隊的人先付款般哼,不允許插隊吴汪,否則可能會出現(xiàn)下面的情況:
先進(jìn)先出,這就是典型的“隊列”蒸眠。
簡單回顧jdk里的隊列
這里簡單講一下以下倆種隊列
1漾橙、阻塞隊列:
ArrayBlockingQueue: Object[] + count + lock.condition(notEmpty、notFull)
入隊:
不阻塞:add楞卡、offer 滿了直接報錯
阻塞:put 滿了:notFull.await();(當(dāng)出隊和刪除元素時喚醒put操作)
出隊:
take():當(dāng)空時,notEmpty.await();當(dāng)有元素入隊時喚醒.
poll():當(dāng)空時直接返回null
LinkedBlockingQueue:Node實現(xiàn)霜运、加鎖(讀鎖、寫鎖分離)蒋腮、可選的有界隊列淘捡。需要考慮實際使用中的內(nèi)存問題,防止溢出池摧。
應(yīng)用:Eexcutors默認(rèn)是使用LinkedBlockingQueue焦除,但是在實際應(yīng)用中,更應(yīng)該手動創(chuàng)建線程池使用有界隊列作彤,防止生產(chǎn)者生產(chǎn)過快膘魄,導(dǎo)致內(nèi)存溢出。
2竭讳、延遲隊列:
DelayQueue : PriorityQueue + Lock.condition + leader
PriorityQueue優(yōu)先級隊列
condition 延遲等待
leader 避免不必要的kong等待
方法:
getDelay()延遲時間
compareTo()通過該方法比較從PriorityQueue里取值
入隊:
add瓣距、put、offer:入隊時會將換喚醒等待中的線程代咸,進(jìn)行一次出隊處理
出隊:
take()阻塞:
1蹈丸、如果隊列里無數(shù)據(jù),元素入隊時會被喚醒
2呐芥、有數(shù)據(jù)逻杖,會阻塞至?xí)r間滿足
poll():滿足隊列有數(shù)據(jù)并且delay時間不大于0會取出元素,否則立即返回null—可能會搶占成為leader
還有優(yōu)先級隊列等就不一一細(xì)說思瘟,有興趣的同學(xué)可以去看一下荸百。
應(yīng)用:延時任務(wù):設(shè)置任務(wù)延遲多久執(zhí)行;需要設(shè)置過期值的處理滨攻,例如緩存過期够话,實現(xiàn)方式:每次getDealy()方法提供一個緩存創(chuàng)建時間與當(dāng)前時間的差值,出隊時compareTo()方法取差值最小的光绕。每次入隊時都會重新取出隊列里差值最小的值進(jìn)行處理女嘲。
??我們使用隊列的,更多的是像生產(chǎn)者诞帐、消費(fèi)者這種場景欣尼。這種場景大多數(shù)情況又對處理速度有著要求,所以我們會使用多線程技術(shù)停蕉。使用多線程就可能會出現(xiàn)并發(fā)愕鼓,為了避免出錯钙态,我們會選擇線程安全的隊列。例如ArrayBlockingQueue菇晃、LinkedBlockingQueue或者是ConcurrentLinkedQueue册倒,前倆者是通過加鎖取實現(xiàn),后面一種是通過cas去實現(xiàn)線程安全磺送。但是又要考慮到生產(chǎn)者過快可能造出的內(nèi)存溢出的問題驻子,所以看起來ArrayBlockingQueue是最符合要求的。但是恰恰加鎖效率又是最慢的册着,所以就引出了我們今天需要討論的主題:Disruptor拴孤!
比較:
ArrayBlockingQueue VS Disruptor
看代碼脾歧。甲捏。。
介紹
??Martin Fowler在自己網(wǎng)站上寫了一篇LMAX架構(gòu)的文章鞭执,在文章中他介紹了LMAX是一種新型零售金融交易平臺司顿,它能夠以很低的延遲產(chǎn)生大量交易。這個系統(tǒng)是建立在JVM平臺上兄纺,其核心是一個業(yè)務(wù)邏輯處理器大溜,它能夠在一個線程里每秒處理6百萬訂單。業(yè)務(wù)邏輯處理器完全是運(yùn)行在內(nèi)存中(圈起來要考)估脆,使用事件源驅(qū)動方式钦奋。業(yè)務(wù)邏輯處理器的核心是Disruptor。
接下來我們來看一下disruptor是如何做到無阻塞疙赠、多生產(chǎn)付材、多消費(fèi)的。
EventFactory:創(chuàng)建消息(任務(wù))的工廠類
ringBufferSize:容器的長度
Executor:消費(fèi)者線程池圃阳,執(zhí)行任務(wù)的線程
ProductType:生產(chǎn)者類型:單生產(chǎn)者厌衔、多生產(chǎn)者
WaitStrategy:等待策略
下面簡單看一下disruptor的代碼。
看代碼捍岳。富寿。。
可以看出在調(diào)用了start()方法后锣夹,消費(fèi)者線程就已經(jīng)開啟页徐,其中涉及到一個重要的概念:EventProcessor:
BatchEventProcessor主要事件循環(huán),處理disruptor中的event银萍,擁有消費(fèi)者的Sequence
另一個核心概念:RingBuffer:它是一個首尾相接的環(huán)狀的容器泞坦,用來在多線程中傳遞數(shù)據(jù)∽┣辏可以看到我們進(jìn)行生產(chǎn)者時贰锁,先從ringbuffer里拿赃梧,再進(jìn)行投遞。
這里使用next()獲得的序號為數(shù)組中下一個可用的元素豌熄,再get(seq)獲取到該位置的元素授嘀,再進(jìn)行賦值處理。
這里的序號是如何產(chǎn)生的呢锣险?
Sequence:順序遞增的序號來編號蹄皱,管理交換的數(shù)據(jù)。生產(chǎn)者和消費(fèi)者都會有維護(hù)自己的Sequence芯肤,通過進(jìn)行比較巷折,來平衡生產(chǎn)者和消費(fèi)者的關(guān)系。消除偽共享(填充緩存行)崖咨。
Sequencer:在生產(chǎn)者和消費(fèi)者之間快速锻拘、正確的傳遞數(shù)據(jù)的并發(fā)算法
Sequence Barrier:序號柵欄,用來平衡生產(chǎn)者和消費(fèi)者之間的關(guān)系
上面說到ringBuffer有定義長度击蹲,說明是一個有界的隊列署拟,那么可能會出現(xiàn)以下倆種情況:當(dāng)消費(fèi)者消費(fèi)速度大于生產(chǎn)者生產(chǎn)者速度,生產(chǎn)者還未來得及往隊列寫入歌豺,或者生產(chǎn)者生產(chǎn)速度大于消費(fèi)者消費(fèi)速度推穷,此時怎么辦呢?
常用的WaitStrategy等待策略(消費(fèi)者等待)
BlockingWaitStrategy使用了鎖类咧,低效的策略馒铃。
SleepingWaitStrategy對生產(chǎn)者線程的影響最小,適合用于異步日志類似的場景痕惋。(不加鎖空等)
YieldingWaitStrategy性能最好区宇,適合用于低延遲的系統(tǒng),在要求極高性能且之間處理線數(shù)小于cpu邏輯核心數(shù)的場景中血巍,推薦使用萧锉。(無鎖策略。主要是使用了Thread.yield()多線程交替執(zhí)行)
至此述寡,disruptor的基本核心概念已經(jīng)介紹完畢柿隙!
Disruptor多邊形操作:
如何實現(xiàn)第一張圖里的多邊形操作?
disruptor.handleEventsWith(E1, E2);
disruptor.after(E1).handleEventsWith(E3);
disruptor.after(E2).handleEventsWith(E4);
disruptor.after(E3, E4).handleEventsWith(E5);
有興趣的同學(xué)可以試一下鲫凶!
再了解了disruptor的核心概念和看了代碼之后禀崖,就可以繼續(xù)學(xué)習(xí)disruptor的多生產(chǎn)多消費(fèi)模型了,disruptor的多線程才能發(fā)揮真正的力量螟炫!
多生產(chǎn)多消費(fèi)模型:
簡單看一下代碼波附。。。
簡單分析掸屡,多個生產(chǎn)者同時向ringbuffer投遞數(shù)據(jù)封寞,假設(shè)此時倆個生產(chǎn)者將ringbuffer已經(jīng)填滿,因為sequence的序號是自增+1(若不滿足獲取條件則循環(huán)掛起當(dāng)前線程)仅财,所以生產(chǎn)的時候能保證線程安全狈究,只需要一個sequence即可。當(dāng)多消費(fèi)者來消費(fèi)的時候盏求,因為消費(fèi)速度不同抖锥,例如消費(fèi)者1來消費(fèi)0、1碎罚,消費(fèi)者2消費(fèi)2磅废、4,消費(fèi)者3消費(fèi)3荆烈。當(dāng)消費(fèi)者消費(fèi)完0后拯勉,消費(fèi)者2消費(fèi)完2后,消費(fèi)者3消費(fèi)完3后耙考,生產(chǎn)者再往隊列投遞數(shù)據(jù)時谜喊,其他位置還未被消費(fèi)潭兽,會投遞到第0個位置倦始, 此時再想投遞數(shù)據(jù)時,雖然消費(fèi)2的第二個位置空缺山卦、消費(fèi)者3的第三個位置空缺鞋邑,消費(fèi)者還在消費(fèi)1時,無法繼續(xù)投遞账蓉。因為是通過比較消費(fèi)者自身維護(hù)的sequence的最小的序號枚碗,來進(jìn)行比較。
應(yīng)用:
Apache Storm铸本、Camel肮雨、Log4j 2
Log4j2 example:
使用了實現(xiàn)EventTranslator的提交機(jī)制。
可參考美團(tuán)文章:https://tech.meituan.com/2016/11/18/disruptor.html中指出:美團(tuán)在公司內(nèi)部統(tǒng)一推行日志接入規(guī)范箱玷,要求必須使用Log4j 2怨规,使普通單機(jī)QPS的上限不再只停留在幾千,極高地提升了服務(wù)性能锡足。
over波丰。~舶得!