每個(gè)經(jīng)典的系統(tǒng)服務(wù)組件,我們總是能發(fā)現(xiàn)其賴以高性能的機(jī)制。
所以我們這次也是一樣镰惦,借著Redis的專題,用實(shí)例來(lái)寫點(diǎn)對(duì)Reactor模式的理解犬绒。
首先我們免不了俗旺入,簡(jiǎn)單介紹下Reactor的相關(guān)概念:
Reactor模式的角色構(gòu)成(Reactor模式一共有5中角色構(gòu)成):
Handle(句柄或描述符,在Windows下稱為句柄,在Linux下稱為描述符):本質(zhì)上表示一種資源(比如說(shuō)文件描述符茵瘾,或是針對(duì)網(wǎng)絡(luò)編程中的socket描述符)礼华,是由操作系統(tǒng)提供的;該資源用于表示一個(gè)個(gè)的事件拗秘,事件既可以來(lái)自于外部圣絮,也可以來(lái)自于內(nèi)部;外部事件比如說(shuō)客戶端的連接請(qǐng)求雕旨,客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)等扮匠;內(nèi)部事件比如說(shuō)操作系統(tǒng)產(chǎn)生的定時(shí)事件等。它本質(zhì)上就是一個(gè)文件描述符凡涩,Handle是事件產(chǎn)生的發(fā)源地棒搜。
Synchronous Event Demultiplexer(同步事件分離器):它本身是一個(gè)系統(tǒng)調(diào)用,用于等待事件的發(fā)生(事件可能是一個(gè)活箕,也可能是多個(gè))力麸。調(diào)用方在調(diào)用它的時(shí)候會(huì)被阻塞,一直阻塞到同步事件分離器上有事件產(chǎn)生為止讹蘑。對(duì)于Linux來(lái)說(shuō)末盔,同步事件分離器指的就是常用的I/O多路復(fù)用機(jī)制,比如說(shuō)select座慰、poll、epoll等翠拣。在Java NIO領(lǐng)域中版仔,同步事件分離器對(duì)應(yīng)的組件就是Selector;對(duì)應(yīng)的阻塞方法就是select方法误墓。
Event Handler(事件處理器):本身由多個(gè)回調(diào)方法構(gòu)成蛮粮,這些回調(diào)方法構(gòu)成了與應(yīng)用相關(guān)的對(duì)于某個(gè)事件的反饋機(jī)制。在Java NIO領(lǐng)域中并沒(méi)有提供事件處理器機(jī)制讓我們調(diào)用或去進(jìn)行回調(diào)谜慌,是由我們自己編寫代碼完成的然想。Netty相比于Java NIO來(lái)說(shuō),在事件處理器這個(gè)角色上進(jìn)行了一個(gè)升級(jí)欣范,它為我們開發(fā)者提供了大量的回調(diào)方法变泄,供我們?cè)谔囟ㄊ录a(chǎn)生時(shí)實(shí)現(xiàn)相應(yīng)的回調(diào)方法進(jìn)行業(yè)務(wù)邏輯的處理,即恼琼,ChannelHandler妨蛹。ChannelHandler中的方法對(duì)應(yīng)的都是一個(gè)個(gè)事件的回調(diào)。
Concrete Event Handler(具體事件處理器):是事件處理器的實(shí)現(xiàn)晴竞。它本身實(shí)現(xiàn)了事件處理器所提供的各種回調(diào)方法蛙卤,從而實(shí)現(xiàn)了特定于業(yè)務(wù)的邏輯。它本質(zhì)上就是我們所編寫的一個(gè)個(gè)的處理器實(shí)現(xiàn)。
Initiation Dispatcher(初始分發(fā)器):實(shí)際上就是Reactor角色颤难。它本身定義了一些規(guī)范神年,這些規(guī)范用于控制事件的調(diào)度方式,同時(shí)又提供了應(yīng)用進(jìn)行事件處理器的注冊(cè)行嗤、刪除等設(shè)施已日。它本身是整個(gè)事件處理器的核心所在,Initiation Dispatcher會(huì)通過(guò)Synchronous Event Demultiplexer來(lái)等待事件的發(fā)生昂验。一旦事件發(fā)生捂敌,Initiation Dispatcher首先會(huì)分離出每一個(gè)事件,然后調(diào)用事件處理器既琴,最后調(diào)用相關(guān)的回調(diào)方法來(lái)處理這些事件占婉。Netty中ChannelHandler里的一個(gè)個(gè)回調(diào)方法都是由bossGroup或workGroup中的某個(gè)EventLoop來(lái)調(diào)用的。
根據(jù)并發(fā)支撐要求等級(jí)分為以下幾種模式
單線程Reactor模式
這是Redis目前采用的實(shí)現(xiàn)方式甫恩,通過(guò)主線程去響應(yīng)處理IO事件逆济,逐條執(zhí)行命令;而其中Redis能夠支撐高并發(fā)和快速響應(yīng)的原因如下:
- redis是基于內(nèi)存的磺箕,內(nèi)存的讀寫速度非辰被牛快。
- redis是單線程的松靡,省去了很多上下文切換線程的時(shí)間简僧。
- redis使用多路復(fù)用技術(shù),可以處理并發(fā)的連接雕欺。
該模式整體流程是:
① 服務(wù)器端的Reactor是一個(gè)線程對(duì)象岛马,該線程會(huì)啟動(dòng)事件循環(huán),并使用Selector來(lái)實(shí)現(xiàn)IO的多路復(fù)用屠列。注冊(cè)一個(gè)Acceptor事件處理器到Reactor中啦逆,Acceptor事件處理器所關(guān)注的事件是ACCEPT事件,這樣Reactor會(huì)監(jiān)聽客戶端向服務(wù)器端發(fā)起的連接請(qǐng)求事件(ACCEPT事件)笛洛。
② 客戶端向服務(wù)器端發(fā)起一個(gè)連接請(qǐng)求夏志,Reactor監(jiān)聽到了該ACCEPT事件的發(fā)生并將該ACCEPT事件派發(fā)給相應(yīng)的Acceptor處理器來(lái)進(jìn)行處理。Acceptor處理器通過(guò)accept()方法得到與這個(gè)客戶端對(duì)應(yīng)的連接(SocketChannel)苛让,然后將該連接所關(guān)注的READ事件以及對(duì)應(yīng)的READ事件處理器注冊(cè)到Reactor中沟蔑,這樣一來(lái)Reactor就會(huì)監(jiān)聽該連接的READ事件了◎蚬睿或者當(dāng)你需要向客戶端發(fā)送數(shù)據(jù)時(shí)溉贿,就向Reactor注冊(cè)該連接的WRITE事件和其處理器。
③ 當(dāng)Reactor監(jiān)聽到有讀或者寫事件發(fā)生時(shí)浦旱,將相關(guān)的事件派發(fā)給對(duì)應(yīng)的處理器進(jìn)行處理宇色。比如,讀處理器會(huì)通過(guò)SocketChannel的read()方法讀取數(shù)據(jù),此時(shí)read()操作可以直接讀取到數(shù)據(jù)宣蠕,而不會(huì)堵塞與等待可讀的數(shù)據(jù)到來(lái)例隆。
④ 每當(dāng)處理完所有就緒的感興趣的I/O事件后,Reactor線程會(huì)再次執(zhí)行select()阻塞等待新的事件就緒并將其分派給對(duì)應(yīng)處理器進(jìn)行處理抢蚀。
根據(jù)Redis單線程處理設(shè)計(jì)镀层,后續(xù)的升級(jí)演進(jìn),猜測(cè)很可能是走單線程多Reactor模式
一個(gè)典型的Redis最佳實(shí)踐是皿曲,不推薦使用big key唱逢,因?yàn)閎ig key的操作會(huì)降低Reactor的響應(yīng)速度。單線程多Reactor模式可以有效將連接動(dòng)作和讀寫動(dòng)作分離屋休,提高吞吐量坞古。TODO:后續(xù)的文章會(huì)寫一個(gè)關(guān)于redis生產(chǎn)使用過(guò)程中,因輸入緩沖爆滿導(dǎo)致redis崩潰的例子劫樟。
線程池Reactor模式
相對(duì)于單線程Reactor模式痪枫,Reactor將業(yè)務(wù)相關(guān)的解碼+計(jì)算+編碼工作(非I/O操作),交付給了線程池叠艳,Reactor無(wú)需關(guān)注業(yè)務(wù)相關(guān)的工作奶陈,其中線程池中的線程能夠復(fù)用,也不會(huì)引入太多的線程創(chuàng)建和切換的開銷附较;從而簡(jiǎn)化Reactor的工作吃粒,使得Reactor專注accept(),read()和write()等IO業(yè)務(wù)拒课;
線程池多Reactor模式
該模式整體流程是:
① 注冊(cè)一個(gè)Acceptor事件處理器到mainReactor中声搁,Acceptor事件處理器所關(guān)注的事件是ACCEPT事件,這樣mainReactor會(huì)監(jiān)聽客戶端向服務(wù)器端發(fā)起的連接請(qǐng)求事件(ACCEPT事件)捕发。啟動(dòng)mainReactor的事件循環(huán)。
② 客戶端向服務(wù)器端發(fā)起一個(gè)連接請(qǐng)求很魂,mainReactor監(jiān)聽到了該ACCEPT事件并將該ACCEPT事件派發(fā)給Acceptor處理器來(lái)進(jìn)行處理扎酷。Acceptor處理器通過(guò)accept()方法得到與這個(gè)客戶端對(duì)應(yīng)的連接(SocketChannel),然后將這個(gè)SocketChannel傳遞給subReactor線程池遏匆。
③ subReactor線程池分配一個(gè)subReactor線程給這個(gè)SocketChannel法挨,即,將SocketChannel關(guān)注的READ事件以及對(duì)應(yīng)的READ事件處理器注冊(cè)到subReactor線程中幅聘。當(dāng)然你也注冊(cè)WRITE事件以及WRITE事件處理器到subReactor線程中以完成I/O寫操作凡纳。Reactor線程池中的每一Reactor線程都會(huì)有自己的Selector、線程和分發(fā)的循環(huán)邏輯帝蒿。
④ 當(dāng)有I/O事件就緒時(shí)荐糜,相關(guān)的subReactor就將事件派發(fā)給響應(yīng)的處理器處理。注意,這里subReactor線程只負(fù)責(zé)完成I/O的read()操作暴氏,在讀取到數(shù)據(jù)后將業(yè)務(wù)邏輯的處理放入到線程池中完成延塑,若完成業(yè)務(wù)邏輯后需要返回?cái)?shù)據(jù)給客戶端,則相關(guān)的I/O的write操作還是會(huì)被提交回subReactor線程來(lái)完成答渔。
【多Reactor線程模式將“接受客戶端的連接請(qǐng)求”和“與該客戶端的通信”分在了兩個(gè)Reactor線程來(lái)完成】
mainReactor完成接收客戶端連接請(qǐng)求的操作关带,它不負(fù)責(zé)與客戶端的通信,而是將建立好的連接轉(zhuǎn)交給subReactor線程來(lái)完成與客戶端的通信沼撕,這樣一來(lái)就不會(huì)因?yàn)閞ead()數(shù)據(jù)量太大而導(dǎo)致后面的客戶端連接請(qǐng)求得不到即時(shí)處理的情況宋雏。并且多Reactor線程模式在海量的客戶端并發(fā)請(qǐng)求的情況下,還可以通過(guò)實(shí)現(xiàn)subReactor線程池來(lái)將海量的連接分發(fā)給多個(gè)subReactor線程务豺,在多核的操作系統(tǒng)中這能大大提升應(yīng)用的負(fù)載和吞吐量磨总。
TODO:等有時(shí)間了實(shí)現(xiàn)一個(gè)多Reactor線程池模式的Demo放上來(lái)。