網(wǎng)絡(luò)IO模型通過內(nèi)核的不斷升級阳柔,現(xiàn)在已經(jīng)有5種模式,這5種模式各有利弊蚓峦。
五種IO模型
阻塞IO
監(jiān)聽一個fd,傳遞給內(nèi)核舌剂,然后阻塞等待济锄,內(nèi)核發(fā)現(xiàn)此fd有事件產(chǎn)生時返回fd以及事件
非阻塞IO
監(jiān)聽一個fd,傳遞給內(nèi)核霍转,然后不管fd是否有事件發(fā)生荐绝,直接返回處理其他事件,但會一直輪詢問內(nèi)核是否fd有事件發(fā)生.這樣用戶空間一直對系統(tǒng)調(diào)用谴忧,造成cpu資源浪費很泊,減低效率
異步IO
當(dāng)前只有windows的iocp屬于異步io。異步io必須滿足2個條件:
- 內(nèi)核到用戶空間是無阻塞的沾谓。
- 硬件到內(nèi)核是無阻塞的
而其他IO模型內(nèi)核空間拷貝數(shù)據(jù)到用戶態(tài)是阻塞的委造,只有iocp是系統(tǒng)內(nèi)核執(zhí)行完成后才告知用戶。
信號驅(qū)動IO
監(jiān)聽一個fd,傳遞給內(nèi)核均驶,非阻塞昏兆,后續(xù)內(nèi)核收到此fd事件變化,發(fā)送一個信號給用戶妇穴。
多路復(fù)用IO
前面的這些IO模型都是監(jiān)聽一個fd,如果系統(tǒng)有100萬個連接爬虱,那么則需要去實現(xiàn)100萬個線程去監(jiān)聽這些IO,這樣對系統(tǒng)來說肯定是并發(fā)很低的腾它。我們?yōu)榱私鉀Q這個問題跑筝,產(chǎn)生了多路復(fù)用IO,也就是說瞒滴,一次性監(jiān)聽多個fd,然后內(nèi)核發(fā)現(xiàn)fd這些fd的某些有事件觸發(fā)曲梗,則返回。對于這種模型linux有3種實現(xiàn)方式
- select
// readfds:關(guān)心讀的fd集合妓忍;writefds:關(guān)心寫的fd集合虏两;excepttfds:異常的fd集合
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
傳遞3個不同事件監(jiān)聽類型fd數(shù)組下去,然后內(nèi)核對這些數(shù)據(jù)進行for循環(huán)世剖。如果對應(yīng)數(shù)組有對應(yīng)事件產(chǎn)生定罢,則返回通知,如果timeout內(nèi)沒有任何事件旁瘫,會告訴超時祖凫。時間復(fù)雜度比較高,是O(n)酬凳。
select基本所有平臺都支持蝙场,但是select缺點除了時間復(fù)雜度以外,還有只支持最大連接數(shù)1024粱年,如果要修改這個連接數(shù)售滤,需要修改常量后重新編譯內(nèi)核。
- poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
poll傳遞的參數(shù)是一個fd以及events在一起的結(jié)構(gòu)體數(shù)組。在內(nèi)核也是對這個數(shù)據(jù)進行for循環(huán)完箩,遍歷查看是否有對應(yīng)事件產(chǎn)生赐俗。但是這個是沒有最大連接數(shù)的限制的。
從上面看弊知,select 和 poll 都需要在返回后阻逮,通過遍歷文件描述符來獲取已經(jīng)就緒的 socket。事實上秩彤,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態(tài)叔扼,因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會線性下降漫雷。比如我有100萬的連接數(shù)瓜富,但當(dāng)前活躍的可能只有1000個,但是select和poll都需要遍歷100萬次降盹,對效率大打折扣
- epoll
//創(chuàng)建epollFd与柑,底層是在內(nèi)核態(tài)分配一段區(qū)域,底層數(shù)據(jù)結(jié)構(gòu)紅黑樹+雙向鏈表
int epoll_create(int size)蓄坏;//創(chuàng)建一個epoll的句柄价捧,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大
//往紅黑樹中增加、刪除涡戳、更新管理的socket fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)结蟋;
//這個api是用來在第一階段阻塞,等待就緒的fd渔彰。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll對fd的監(jiān)聽模式有2種可以選擇嵌屎,LT以及ET
- LT 水平觸發(fā)
無論fd是否發(fā)生變化,每次都會告知用戶態(tài)當(dāng)前fd的狀態(tài)如何 - ET 邊緣觸發(fā)
只有fd發(fā)生變化胳岂,才會告知用戶態(tài)编整,告知之后舔稀,下一次不會再告訴當(dāng)前fd的狀態(tài)信息
Reactor模型介紹
Reactor模型指在使用多路復(fù)用IO時乳丰,對于用戶態(tài)fd的事件管理模型
Reactor單線程模型
業(yè)務(wù)邏輯處理和fd的讀寫IO都在同一個線程實現(xiàn)
Reactor多線程模型
業(yè)務(wù)邏輯處理和fd的讀寫IO不在同一個線程實現(xiàn)
multi-reactor 多線程模型
業(yè)務(wù)處理多線程以及讀寫IO的事件分發(fā)機制也采用多線程處理
Redis的網(wǎng)絡(luò)IO模型
Redis采用單線程為何支持高并發(fā)
- Redis使用的內(nèi)存IO,不是磁盤IO内贮,大大降低了IO時間
- Redis單線程产园,無需去考慮多線程造成的死鎖問題
- Redis單線程,底層網(wǎng)絡(luò)IO模型使用多路復(fù)用epoll方式(如果內(nèi)核不支持epoll,可自動切換到select或者poll夜郁,看配置信息可進行修改)
Redis6實現(xiàn)了多線程什燕,作用是什么
Redis6實現(xiàn)的多線程,只是對網(wǎng)絡(luò)IO讀寫處理做多線程處理竞端,但是對命令行的操作仍然是單線程的屎即。這樣即加快了IO處理效率,又保證了原子性。