面试必会系列 - 5.1 网络BIO、NIO、epoll,同步/异步模型、阻塞/非阻塞模型,你能分清吗?
本文已收錄至 Github(MD-Notes),若博客中圖片模糊或打不開,可以來我的 Github 倉庫,包含了完整圖文:https://github.com/HanquanHq/MD-Notes,涵蓋了互聯網大廠面試必問的知識點,講解透徹,長期更新中,歡迎一起學習探討 ~
更多內容,可以訪問:
面試必會系列專欄:https://blog.csdn.net/sinat_42483341/category_10300357.html
操作系統系列專欄:https://blog.csdn.net/sinat_42483341/category_10519484.html
目錄
- BIO,NIO,epoll
- 同步/異步模型、阻塞/非阻塞模型
- 同步、異步
- 阻塞、非阻塞
- 那么,BIO NIO AIO 就可以簡單的理解為:
- BIO 同步阻塞
- NIO 同步非阻塞
- 多路復用器 select poll epoll 同步非阻塞
- epoll 同步非阻塞
- 邊沿觸發
- 水平觸發
- AIO
- 關于 Linux 上的 同步/異步 IO
- Netty
BIO,NIO,epoll
同步/異步模型、阻塞/非阻塞模型
同步:當一個同步調用發出后,調用者要一直等待返回結果。通知后,才能進行后續的執行。
異步:當一個異步過程調用發出后,調用者不能立刻得到返回結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者。
阻塞:是指調用結果返回前,當前線程會被掛起,即阻塞。
非阻塞:是指即使調用結果沒返回,也不會阻塞當前線程。
比喻:
小Q去釣魚,拋完線后就傻傻的看著有沒有動靜,有則拉桿(同步阻塞)
小Q去釣魚,拿魚網撈一下,有沒有魚立即知道,不用等,直接就撈(同步非阻塞)
小Q去釣魚,這個魚缸比較牛皮,扔了后自己就打王者榮耀去了,因為魚上鉤了這個魚缸帶的報警器會通知我。這樣實現異步(異步非阻塞)
同步、異步
客戶端在請求數據的過程中,能否做其他事情
- 同步模型:程序自己讀取,程序在 IO 上的模型就叫 同步模型
- 異步模型:程序把讀取的過程交給內核,自己做自己的事情,叫 異步模型
同步的意思是:客戶端與服務端 相同步調。就是說 服務端 沒有把數據給 客戶端 之前,客戶端什么都不能做。它們做同樣一件事情,就是說它們有相同步調,即同步。
阻塞、非阻塞
客戶端與服務端是否從頭到尾始終都有一個持續連接,以至于 占用了通道,不讓其他客戶端成功連接。
阻塞的意思是:客戶端與服務端之間是否始終有個東西占據著它們中間的通道。就是說 客戶端與服務端中間,始終有一個連接。導致其他客戶端不能繼續建立新通道連接服務器。
那么,BIO NIO AIO 就可以簡單的理解為:
- BIO(同步阻塞):客戶端在請求數據的過程中,保持一個連接(阻塞),不能做其他事情(同步)。
- NIO(同步非阻塞):客戶端在請求數據的過程中,不用保持一個連接(非阻塞),不能做其他事情。(許多個小連接,也就是輪詢)
- AIO(異步非阻塞):客戶端在請求數據的過程中,不用保持一個連接,可以做其他事情。(客戶端做其他事情,數據來了等服務端來通知。)
BIO 同步阻塞
-
阻塞式的 ServerSocket
- 阻塞地等待客戶端的連接,連接后拋出一個線程
- 阻塞地等待客戶端發送消息
- C10K 問題,如果有1萬個連接,就要拋出1萬個線程
NIO 同步非阻塞
Java:New IO 新的 IO 包
OS:Non-Blocking IO 非阻塞的 IO
-
非阻塞式的 ServerSocketChannel
- ss.configBlocking(false) 設置非阻塞
- 在accept(3,中空轉,要么返回client的描述符,要么返回-1,如果拿到了新的連接,調用clientList.add()
-
缺點
-
ByteBuffer只有一個指針,用起來有很多坑
-
NIO的存在的問題:C10K問題
- 放大:C10K當并發量很大的時候,需要遍歷的文件描述符會很多,**每循環 **一次,都要調用 10K 次recv 系統調用(復雜度O(n)),進行用戶態到內核態的切換,性能損耗大。
- 縮小:你調用了 那么多次 recv 系統調用,但是如果 C10K 只有 1 個 Client 發來了數據,只有 1 次系統調用是有效的,剩余 n-1 次的監聽都是無效的。我們希望,能夠有一種方式,只需要調用 有效 的 recv 系統調用 就可以。
-
多路復用器 select poll epoll 同步非阻塞
OS 提供的 多路復用器 有 select, pool, epoll, kqueue 等
select,poll 的缺點是,每次你調用的時候,都需要將所有的文件描述符的集合 fds 作為參數傳遞給函數,而 epoll 的優勢是在內核中開辟了一個空間(調用的是 epoll_create)
Java 把所有的多路復用器封裝成了 Selector 類
可以在啟動時,指定使用哪種多路復用器(默認優先選擇 epoll),注意 windows 上是沒有 epoll 的。
-Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider-
允許程序去調用內核,來監控更多的客戶端,直到一個或多個文件描述符是可用的狀態,再返回。減少了用戶態、內核態的無用切換。
-
Linux 內核提供的 select 多路復用器,會返回給程序一個 list,告訴程序 哪些 fd 是可以讀/寫的狀態,然后程序要自己讀取,這是 IO 同步模型。
只有當 read 系統調用不需要你程序自己去做的時候,才屬于異步的 IO,目前只有 windows iocp 實現了。
-
缺點
- 如果有很多長連接,內核每次都要給程序傳遞很多連接對象
epoll 同步非阻塞
Nginx, Redis(C寫的) 底層都使用了 epoll
strace 命令用來監控指定的程序(字節碼)發生了哪些系統調用
-
epoll 也是多路復用器,但是它有一個 存放結果集的鏈表,它與 select / poll 的區別如下:
-
select:應用程什么時候調用 select,內核就什么時候遍歷所有的文件描述符,修正 fd 的狀態。
-
epoll:應用程序在內核的 紅黑樹 中存放過一些 fd,那么,內核基于中斷處理完 fd 的 buffer/狀態 之后,繼續把有狀態的 fd 拷貝到鏈表中。
即便應用程序不調用內核,內核也會隨著中斷,完成所有fd狀態的設置。這樣,程序調用epoll_wait可以及時去取鏈表包含中有狀態的 fd 的結果集。規避了對于文件描述符的全量遍歷。
拿到 fd 的結果集之后,程序需要自己取處理 accept / recv 等系統調用的過程。所以epoll_wait依然是同步模型。
-
-
Epoll 是 Event poll,把有數據這個事件通知給程序,它不負責讀取 IO,還需要程序自己取讀取數據
-
man epoll 幫助文檔:
The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll API can be used either as an edge-triggered(邊緣觸發) or a level-triggered(條件觸發) interface and scales well to large numbers of watched file descriptors(可以很好地擴展到大量監視文件描述符). The following system calls are provided to create and manage an epoll instance. [ 注:這里 (2) 的意思是 2 類系統調用。類似地,還有 7 類雜項]
-
epoll_create creates an epoll instance and returns a file descriptor referring to that instance.
創建成功之后,返回一個 fd 文件描述符 例如 fd6。實際上是在內核開辟一塊空間,里面存放紅黑樹。
-
epoll_ctl, This system call performs control operations on the epoll(7) instance referred to by the file descriptor epfd. It requests that the operation op be performed for the target file descriptor, fd.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epoll_create = 4 // epoll_ctl(4, add, 7, accept)例如,在文件描述符fd6中,使用 EPOLL_CTL_ADD 添加服務器用于 listen 的文件描述符
- 此系統調用對文件描述符 epfd 引用的 epoll(7) 實例執行控制操作,int op 可選參數:EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL
-
epoll_wait waits for I/O events, blocking the calling thread if no events are currently available.
**epoll_wait 不傳遞 fds,不觸發內核遍歷。**你的程序需要寫一個死循環,一直調用 epoll_wait,可以設置阻塞,或者非阻塞
-
-
早期在沒有上述三個系統調用的時候,需要應用程序調用 mmap 來實現兩端的內存共享提速,后期在 2.6 內核版本之后,提供了這些系統調用,就不需要 mmap 這種實現方式了
邊沿觸發
只要緩沖區還有東西可以讀,只要你調用了epoll_wait函數,它就會繼續通知你
水平觸發
就像高低電平一樣,只有從高電平到低電平或者低電平到高電平時才會通知我們。只有客戶端再次向服務器端發送數據時,epoll_wait 才會再返回給你
AIO
- AIO 是異步的模型,直接aio_read,立即返回,并不阻塞進程,然后由內核自行復制數據給 aio_read,這里的 aio_read 是不阻塞進程的,它直接讀內核,不管是否準備好。
- 使用的是 callback / hook / templateMethod 回調,是基于事件模型的 IO
- Netty封裝的是NIO,不是AIO
- AIO 只有 Window 支持(內核中使用CompletionPort完成端口)
- 在 Linux 上的 AIO 只不過是對 NIO 的封裝而已(是基于epoll 的輪詢)
關于 Linux 上的 同步/異步 IO
“停止使用這種只適用于特殊情況的垃圾,讓所有人都在乎的系統核心盡其所能地運行好其基本的性能”,就是說,你cpu做你cpu該做的事,別凈整些沒用的。
就像系統調用那樣,盡管Linux上建立一個新的系統調用非常容易,但并不提倡每出現一種新的抽象就簡單的加入一個新的系統調用。這使得它的系統調用接口簡潔得令人嘆為觀止(2.6版本338個),新系統調用增加頻率很低也反映出它是一個相對較穩定并且功能已經較為完善的操作系統。
這也是為什么以前Linux版本的主內核樹中沒有類似于windows 上 AIO這樣的通用的內核異步IO處理方案(在Linux 上的AIO只不過是對 NIO 的封裝而已),因為不安全,會讓內核做的事情太多,容易出bug。windows敢于這么做,是因為它的市場比較廣,一方面是用戶市場,一方面是服務器市場,況且windows比較注重用戶市場,所以敢于把內核做的胖一些,也是因此雖然現在已經win10了,但是藍屏啊,死機啊,掛機啊這些問題也還是會出現。
現在 Linux 對異步 IO 也開始上心了,根據 https://www.infoq.cn/article/zPhDatkQx5OPKd9J53mX Linux內核發展史,2019.5.5 發布的 5.1 版本的內核,包括用于異步 I/O 的高性能接口 io_uring, 是 Linux 中最新的原生異步 I/O 實現,是良好的 epoll 替代品。
老周說有個 Linux 6.x 版本之后,Linux 有了統一的異步IO實現
參考文獻:
異步IO一直是 Linux 系統的痛。Linux 很早就有 POSIX AIO 這套異步IO實現,但它是在用戶空間自己開用戶線程模擬的,效率極其低下。后來在 Linux 2.6 引入了真正的內核級別支持的異步IO實現(Linux aio),但是它只支持 Direct IO,只支持磁盤文件讀寫,而且對文件大小還有限制,總之各種麻煩。到目前為止(2019年5月),libuv 還是在用pthread+preadv的形式實現異步IO。
隨著 Linux 5.1 的發布,Linux 終于有了自己好用的異步IO實現,并且支持大多數文件類型(磁盤文件、socket,管道等),這個就是本文的主角:io_uring
作者:CarterLi
鏈接:https://segmentfault.com/a/1190000019300089
前面的文章說到 io_uring 是 Linux 中最新的原生異步 I/O 實現,實際上 io_uring 也支持 polling,是良好的 epoll 替代品。
作者:CarterLi
鏈接:https://segmentfault.com/a/1190000019361819?utm_source=tag-newest
Netty
Netty主要用于網絡通信。
- 很多網頁游戲的服務器都是用Netty寫的。
- Tomcat,Zookeeper,很多開源分布式的底層也是netty寫的。
總結
以上是生活随笔為你收集整理的面试必会系列 - 5.1 网络BIO、NIO、epoll,同步/异步模型、阻塞/非阻塞模型,你能分清吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: leetcode 258. 各位相加(J
- 下一篇: 本地 MarkDown 怎么部署到服务器