日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

Redis:事件驱动(IO多路复用)

發(fā)布時(shí)間:2025/3/21 数据库 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis:事件驱动(IO多路复用) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

§??從Redis的工作模式談起

§??Reactor模式

·????????C10K問(wèn)題

·????????I/O多路復(fù)用技術(shù)

·????????Reactor的定義

·????????Java中的NIO與Netty

§??Redis與Reactor

§??總結(jié)

§??參考資料

從Redis的工作模式談起
我們?cè)谑褂肦edis的時(shí)候,通常是多個(gè)客戶端連接Redis服務(wù)器,然后各自發(fā)送命令請(qǐng)求(例如GetSet)到Redis服務(wù)器,最后Redis處理這些請(qǐng)求返回結(jié)果
那Redis服務(wù)端是使用單進(jìn)程還是多進(jìn)程,單線程還是多線程來(lái)處理客戶端請(qǐng)求的呢?

答案是單進(jìn)程單線程

當(dāng)然,Redis除了處理客戶端的命令請(qǐng)求還有諸如RDB持久化AOF重寫(xiě)這樣的事情要做,而在做這些事情的時(shí)候,Redis會(huì)fork(分叉出)子進(jìn)程去完成但對(duì)于accept客戶端連接處理客戶端請(qǐng)求返回命令結(jié)果等等這些,Redis是使用主進(jìn)程及主線程來(lái)完成的。我們可能會(huì)驚訝Redis在使用單進(jìn)程及單線程來(lái)處理請(qǐng)求為什么會(huì)如此高效?在回答這個(gè)問(wèn)題之前,

我們先來(lái)討論一個(gè)I/O多路復(fù)用的模式--Reactor

Reactor模式
C10K問(wèn)題
考慮這樣一個(gè)問(wèn)題:有10000個(gè)客戶端需要連上一個(gè)服務(wù)器并保持TCP連接,客戶端會(huì)不定時(shí)的發(fā)送請(qǐng)求給服務(wù)器,服務(wù)器收到請(qǐng)求后需及時(shí)處理并返回結(jié)果我們應(yīng)該怎么解決?

方案一:我們使用一個(gè)線程來(lái)監(jiān)聽(tīng),當(dāng)一個(gè)新的客戶端發(fā)起連接時(shí),建立連接并new一個(gè)線程來(lái)處理這個(gè)新連接

缺點(diǎn):當(dāng)客戶端數(shù)量很多時(shí),服務(wù)端線程數(shù)過(guò)多,即便不壓垮服務(wù)器,由于CPU有限其性能也極其不理想因此此方案不可用


方案二:我們使用一個(gè)線程監(jiān)聽(tīng),當(dāng)一個(gè)新的客戶端發(fā)起連接時(shí),建立連接并使用線程池處理該連接

優(yōu)點(diǎn):客戶端連接數(shù)量不會(huì)壓垮服務(wù)端

缺點(diǎn):服務(wù)端處理能力受限于線程池的線程數(shù),而且如果客戶端連接中大部分處于空閑狀態(tài)的話服務(wù)端的線程資源被浪費(fèi)


因此,一個(gè)線程僅僅處理一個(gè)客戶端連接無(wú)論如何都是不可接受的,那能不能一個(gè)線程處理多個(gè)連接呢?該線程輪詢每個(gè)連接,如果某個(gè)連接有請(qǐng)求則處理請(qǐng)求,沒(méi)有請(qǐng)求則處理下一個(gè)連接,這樣可以實(shí)現(xiàn)嗎?

答案是肯定的,而且不必輪詢我們可以通過(guò)I/O多路復(fù)用技術(shù)來(lái)解決這個(gè)問(wèn)題

I/O多路復(fù)用技術(shù)(三種里最佳)
現(xiàn)代的UNIX操作系統(tǒng)提供了select/poll/kqueue/epoll這樣的系統(tǒng)調(diào)用,這些系統(tǒng)調(diào)用的功能是:你告知我一批套接字(socket),當(dāng)這些套接字的可讀或可寫(xiě)事件發(fā)生時(shí),我通知你這些事件信息。(IO中講到的,里面的事件分離者。在我的理解有點(diǎn)像中介的味道,在socket和事件處理者中充當(dāng)傳話的角色)、

I/O 多路復(fù)用模塊(整個(gè) I/O 多路復(fù)用模塊在事件循環(huán)看來(lái)就是一個(gè)輸入事件、輸出 aeFiredEvent 數(shù)組的一個(gè)黑箱)


I/O 多路復(fù)用模塊封裝了底層的 select、epoll、avport 以及 kqueue這些 I/O 多路復(fù)用函數(shù)(實(shí)現(xiàn)了handle找實(shí)現(xiàn)的handler過(guò)程),為上層提供了相同的接口。

?

當(dāng)如下任一情況發(fā)生時(shí),會(huì)產(chǎn)生套接字的可讀事件:

§??該套接字的接收緩沖區(qū)中的數(shù)據(jù)字節(jié)數(shù)大于等于套接字接收緩沖區(qū)低水位標(biāo)記的大小;

§??該套接字的讀半部關(guān)閉(也就是收到了FIN),對(duì)這樣的套接字的讀操作將返回0(也就是返回EOF);

§??該套接字是一個(gè)監(jiān)聽(tīng)套接字且已完成的連接數(shù)不為0;

§??該套接字有錯(cuò)誤待處理,對(duì)這樣的套接字的讀操作將返回-1

當(dāng)如下任一情況發(fā)生時(shí),會(huì)產(chǎn)生套接字的可寫(xiě)事件:

§??該套接字的發(fā)送緩沖區(qū)中的可用空間字節(jié)數(shù)大于等于套接字發(fā)送緩沖區(qū)低水位標(biāo)記的大小;

§??該套接字的寫(xiě)半部關(guān)閉,繼續(xù)寫(xiě)會(huì)產(chǎn)生SIGPIPE信號(hào);

§??非阻塞模式下,connect返回之后,該套接字連接成功或失敗;

§??該套接字有錯(cuò)誤待處理,對(duì)這樣的套接字的寫(xiě)操作將返回-1

此外,在UNIX系統(tǒng)上,一切皆文件套接字也不例外,每一個(gè)套接字都有對(duì)應(yīng)的fd(即文件描述符)我們簡(jiǎn)單看看這幾個(gè)系統(tǒng)調(diào)用的原型

select(int nfds, fd_set *r, fd_set *w,fd_set *e, struct timeval *timeout)

對(duì)于select(),我們需要傳3個(gè)集合,r(讀),w(寫(xiě))和e其中,r表示我們對(duì)哪些fd的可讀事件感興趣,w表示我們對(duì)哪些fd的可寫(xiě)事件感興趣每個(gè)集合其實(shí)是一個(gè)bitmap,通過(guò)0/1表示我們感興趣的fd例如,

如:我們對(duì)于fd為6的可讀事件感興趣,那么r集合的第6個(gè)bit需要被設(shè)置為1這個(gè)系統(tǒng)調(diào)用會(huì)阻塞,直到我們感興趣的事件(至少一個(gè))發(fā)生調(diào)用返回時(shí),內(nèi)核同樣使用這3個(gè)集合來(lái)存放fd實(shí)際發(fā)生的事件信息也就是說(shuō),調(diào)用前這3個(gè)集合表示我們感興趣的事件,調(diào)用后這3個(gè)集合表示實(shí)際發(fā)生的事件

select為最早期的UNIX系統(tǒng)調(diào)用,它存在4個(gè)問(wèn)題:

1)這3個(gè)bitmap有大小限制(FD_SETSIZE,通常為1024);

2)由于這3個(gè)集合在返回時(shí)會(huì)被內(nèi)核修改,因此我們每次調(diào)用時(shí)都需要重新設(shè)置

3)我們?cè)谡{(diào)用完成后需要掃描這3個(gè)集合才能知道哪些fd的讀/寫(xiě)事件發(fā)生了,一般情況下全量集合比較大而實(shí)際發(fā)生讀/寫(xiě)事件的fd比較少,效率比較低下;

4)內(nèi)核在每次調(diào)用都需要掃描這3個(gè)fd集合,然后查看哪些fd的事件實(shí)際發(fā)生,在讀/寫(xiě)比較稀疏的情況下同樣存在效率問(wèn)題

由于存在這些問(wèn)題,于是人們對(duì)select進(jìn)行了改進(jìn),從而有了poll

poll(struct pollfd *fds, int nfds, inttimeout)

?

struct pollfd {

int fd;

short events;

short revents;

}

?

poll調(diào)用需要傳遞的是一個(gè)pollfd結(jié)構(gòu)的數(shù)組,調(diào)用返回時(shí)結(jié)果信息也存放在這個(gè)數(shù)組里面pollfd的結(jié)構(gòu)中存放著fd我們對(duì)該fd感興趣的事件(events)以及該fd實(shí)際發(fā)生的事件(revents)poll傳遞的不是固定大小的bitmap,因此

select的問(wèn)題1解決了;poll將感興趣事件和實(shí)際發(fā)生事件分開(kāi)了,因此

select的問(wèn)題2也解決了但

select的問(wèn)題3和問(wèn)題4仍然沒(méi)有解決

select問(wèn)題3比較容易解決,只要系統(tǒng)調(diào)用返回的是實(shí)際發(fā)生相應(yīng)事件的fd集合,我們便不需要掃描全量的fd集合

對(duì)于select的問(wèn)題4,我們?yōu)槭裁葱枰看握{(diào)用都傳遞全量的fd呢?

內(nèi)核可不可以在第一次調(diào)用的時(shí)候記錄這些fd,然后我們?cè)谝院蟮恼{(diào)用中不需要再傳這些fd呢?

問(wèn)題的關(guān)鍵在于無(wú)狀態(tài)對(duì)于每一次系統(tǒng)調(diào)用,內(nèi)核不會(huì)記錄下任何信息,所以每次調(diào)用都需要重復(fù)傳遞相同信息

上帝說(shuō)要有狀態(tài),所以我們有了epoll和kqueue

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd,struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event*events, int maxevents, int timeout);

epoll_create的作用是創(chuàng)建一個(gè)context,這個(gè)context相當(dāng)于狀態(tài)保存者的概念

epoll_ctl的作用是,當(dāng)你對(duì)一個(gè)新的fd的讀/寫(xiě)事件感興趣時(shí),通過(guò)該調(diào)用將fd與相應(yīng)的感興趣事件更新到context中

epoll_wait的作用是,等待context中fd的事件發(fā)生

就是這么簡(jiǎn)單

epoll是Linux中的實(shí)現(xiàn),kqueue則是在FreeBSD的實(shí)現(xiàn)

int kqueue(void);

int kevent(int kq, const struct kevent*changelist, int nchanges, struct kevent *eventlist, int nevents, const structtimespec *timeout);

與epoll相同的是,kqueue創(chuàng)建一個(gè)context;與epoll不同的是,kqueue用kevent代替了epoll_ctl和epoll_wait

epoll和kqueue解決了select存在的問(wèn)題通過(guò)它們,我們可以高效的通過(guò)系統(tǒng)調(diào)用來(lái)獲取多個(gè)套接字的讀/寫(xiě)事件,從而解決一個(gè)線程處理多個(gè)連接的問(wèn)題

Reactor的定義


通過(guò)select/poll/epoll/kqueue這些I/O多路復(fù)用函數(shù)庫(kù),我們解決了一個(gè)線程處理多個(gè)連接的問(wèn)題,但整個(gè)Reactor模式的完整框架是怎樣的呢?參考這篇paper,我們可以對(duì)Reactor模式有個(gè)完整的描述

?

Handles:表示操作系統(tǒng)管理的資源,我們可以理解為fd

Synchronous Event Demultiplexer:同步事件分離器,阻塞等待Handles中的事件發(fā)生

Initiation Dispatcher:初始分派器,作用為添加Event handler(事件處理器)刪除Event handler以及分派事件給Event handler也就是說(shuō),SynchronousEvent Demultiplexer負(fù)責(zé)等待新事件發(fā)生,事件發(fā)生時(shí)通知InitiationDispatcher,然后Initiation Dispatcher調(diào)用event handler處理事件

Event Handler:事件處理器的接口

Concrete Event Handler:事件處理器的實(shí)際實(shí)現(xiàn),而且綁定了一個(gè)Handle因?yàn)樵趯?shí)際情況中,我們往往不止一種事件處理器,因此這里將事件處理器接口和實(shí)現(xiàn)分開(kāi),與C++Java這些高級(jí)語(yǔ)言中的多態(tài)類似

以上各子模塊間協(xié)作的步驟描述如下:(其實(shí)就是項(xiàng)目中所做的基于redis 的異步框架差不多)

1.????我們注冊(cè)Concrete Event Handler到InitiationDispatcher中

2.???Initiation Dispatcher調(diào)用每個(gè)Event Handler的get_handle接口獲取其綁定的Handle

3.???Initiation Dispatcher調(diào)用handle_events開(kāi)始事件處理循環(huán)在這里,InitiationDispatcher會(huì)將步驟2獲取的所有Handle都收集起來(lái),使用Synchronous Event Demultiplexer來(lái)等待這些Handle的事件發(fā)生

4.????當(dāng)某個(gè)(或某幾個(gè))Handle的事件發(fā)生時(shí),Synchronous Event Demultiplexer通知InitiationDispatcher

5.???Initiation Dispatcher根據(jù)發(fā)生事件的Handle找出所對(duì)應(yīng)的Handler

6.????InitiationDispatcher調(diào)用Handler的handle_event方法處理事件

時(shí)序圖如下:


另外,該文章舉了一個(gè)分布式日志處理的例子,感興趣的同學(xué)可以看下

通過(guò)以上的敘述,我們清楚了Reactor的大概框架以及涉及到的底層I/O多路復(fù)用技術(shù)

Java中的NIO與Netty
談到Reactor模式,在這里奉上Java大神Doug Lea的Scalable IO in Java,里面提到了Java網(wǎng)絡(luò)編程中的經(jīng)典模式NIO(非堵塞)以及Reactor,并且有相關(guān)代碼幫助理解,看完后獲益良多

另外,Java的NIO是比較底層的,我們實(shí)際在網(wǎng)絡(luò)編程中還需要自己處理很多問(wèn)題(譬如socket的讀半包),稍不注意就會(huì)掉進(jìn)坑里幸好,我們有了Netty這么一個(gè)網(wǎng)絡(luò)處理框架,免去了很多麻煩

Redis與Reactor
在上面的討論中,我們了解了Reactor模式,那么Redis中又是怎么使用Reactor模式的呢?

首先,Redis服務(wù)器中有兩類事件,文件事件和時(shí)間事件

§??文件事件(file event):Redis客戶端通過(guò)socket與Redis服務(wù)器連接,而文件事件就是服務(wù)器對(duì)套接字操作的抽象例如,客戶端發(fā)了一個(gè)GET命令請(qǐng)求,對(duì)于Redis服務(wù)器來(lái)說(shuō)就是一個(gè)文件事件

§??時(shí)間事件(time event):服務(wù)器定時(shí)或周期性執(zhí)行的事件例如,定期執(zhí)行RDB持久化

在這里我們主要關(guān)注Redis處理文件事件的模型參考Redis的設(shè)計(jì)與實(shí)現(xiàn),Redis的文件事件處理模型是這樣的:


在這個(gè)模型中,Redis服務(wù)器用主線程執(zhí)行I/O多路復(fù)用程序文件事件分派器以及事件處理器而且,盡管多個(gè)文件事件可能會(huì)并發(fā)出現(xiàn),Redis服務(wù)器是順序處理各個(gè)文件事件的

Redis服務(wù)器主線程的執(zhí)行流程在Redis.c的main函數(shù)中體現(xiàn),而關(guān)于處理文件事件的主要的有這幾行:

int main(int argc, char **argv) {

...

initServer();

...

aeMain();

...

aeDeleteEventLoop(server.el);

return 0;

}

在initServer()中,建立各個(gè)事件處理器;在aeMain()中,執(zhí)行事件處理循環(huán);在aeDeleteEventLoop(server.el)中關(guān)閉停止事件處理循環(huán);最后退出

總結(jié)
多路 I/O 復(fù)用模型是利用select、poll、epoll可以同時(shí)監(jiān)察多個(gè)流的 I/O 事件的能力,在空閑的時(shí)候,會(huì)把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)流有I/O事件時(shí),就從阻塞態(tài)中喚醒,于是程序就會(huì)輪詢一遍所有的流(epoll是只輪詢那些真正發(fā)出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無(wú)用操作。這里“多路”指的是多個(gè)網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個(gè)線程。采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò)IO的時(shí)間消耗),且Redis在內(nèi)存中操作數(shù)據(jù)的速度非常快(內(nèi)存內(nèi)的操作不會(huì)成為這里的性能瓶頸),主要以上兩點(diǎn)造就了Redis具有很高的吞吐量。

在這篇文章中,我們從Redis的工作模型開(kāi)始,討論了C10K問(wèn)題、I/O多路復(fù)用技術(shù)、Java的NIO,最后回歸到Redis的Reactor模式中。如有紕漏,懇請(qǐng)大家指出,我會(huì)一一加以勘正。謝謝!
?

總結(jié)

以上是生活随笔為你收集整理的Redis:事件驱动(IO多路复用)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。