IO 多路複用是什麼意思?Redis 中的 IO 又是什麼?

本文首發於我的知乎:https://zhuanlan.zhihu.com/p/632776455

當你打開電腦,任何時候都在進行着 IO 的操作!

比如一次 API 接口調用、向磁盤寫入日誌信息,其實就是在跟 I/O 打交道。

一次 IO 操作分爲等待資源、使用資源兩個階段,以下分別進行介紹。

先補充一下什麼是阻塞與非阻塞 和同步與異步

  1. 同步阻塞 IO(Blocking IO):即傳統的 IO 模型

  2. 同步非阻塞 IO(Non-blocking IO):默認創建的 socket 都是阻塞的,非阻塞 IO 要求 socket 被設置爲 NONBLOCK。在 Java 領域,也稱爲 New I/O

  3. IO 多路複用(IO Multiplexing):即經典的 Reactor 設計模式,有時也稱爲異步阻塞 IO,Java 中的 Selector 和 Linux 中的 epoll 都是這種模型

  4. 異步 IO(Asynchronous IO):即經典的 Proactor 設計模式,也稱爲異步非阻塞 IO

具體展開來講一下:


阻塞與非阻塞 I/O

阻塞與非阻塞 I/O 是對於操作系統內核而言的,發生在等待資源階段,根據發起 I/O 請求是否阻塞來判斷。

阻塞 I/O:這種模式下一個用戶進程在發起一個 I/O 操作之後,只有收到響應或者超時纔可進行處理其它事情,否則 I/O 將會一直阻塞。以讀取磁盤上的一段文件爲例,系統內核在完成磁盤尋道、讀取數據、複製數據到內存中之後,這個調用纔算完成。阻塞的這段時間對 CPU 資源是浪費的。

非阻塞 I/O:這種模式下一個用戶進程發起一個 I/O 操作之後,如果數據沒有就緒,會立刻返回(標誌數據資源不可用),此時 CPU 時間片可以用來做一些其它事情。

同步與異步 I/O

同步與異步 I/O 發生在使用資源階段,根據實際 I/O 操作來判斷。

同步 I/O:應用發送或接收數據後,如果不返回,繼續等待(此處發生阻塞),直到數據成功或失敗返回。

異步 I/O:應用發送或接收數據後立刻返回,數據寫入 OS 緩存,由 OS 完成數據發送或接收,並返回成功或失敗的信息給應用。Node.js 就是典型的異步編程例子。

什麼是 IO 多路複用


IO 多路複用是一種同步 IO 模型,實現一個線程可以監視多個文件句柄;一旦某個文件句柄就緒,就能夠通知應用程序進行相應的讀寫操作;沒有文件句柄就緒時會阻塞應用程序,交出 cpu。多路是指網絡連接,複用指的是同一個線程

IO 多路複用使用的模型


  1. select 模型

  2. poll 模型

  3. epoll

具體展開來講一下

select 模型

select 模型是最古老的 IO 多路複用機制之一,使用 fd_set 數據結構來保存文件描述符集合,並提供了 select() 函數來等待文件描述符的就緒狀態。它有一個限制,即所監視的文件描述符數量有一個上限,通常是 1024。

每次調用 select() 時,都需要將整個文件描述符集合從用戶空間複製到內核空間,這可能帶來性能問題。

poll 模型

是對 select 的改進,它使用 pollfd 結構體數組來保存文件描述符和事件信息,並提供了 poll() 函數來等待文件描述符的就緒狀態。相對於 select,poll 沒有文件描述符數量的限制,因爲它使用動態分配的數組來保存文件描述符信息。

但與 select 類似,每次調用 poll() 時,也需要將整個結構體數組從用戶空間複製到內核空間。

epoll 模型

是 Linux 特有的 IO 多路複用機制,自從 2.5.44 內核版本引入後成爲主流。它使用基於事件的方式來管理文件描述符,使用一個事件表(event table)來保存文件描述符和事件信息,並提供了 epoll_create()、epoll_ctl() 和 epoll_wait() 等函數來操作事件表。

相對於 select 和 poll,epoll 具有更好的性能,因爲它採用了事件驅動的方式,不需要在每次調用時複製整個事件表。

PS:很多文章在談論到 BIO、NIO、AIO 的時候僅僅是拋出一堆定義,以及一些生動的例子。看似很好理解。但是並沒有將最基礎的本質原理顯現出來,如果沒有沒有從 IO 的原理出發的話是很難理解這三者之間的區別的。所以本篇文章從 Java 是如何進行 IO 操作爲開頭進行分析。

終端研發部:這是我看過對 bio,nio,aio 解釋的最透徹的文章!!!151 贊同 · 6 評論文章

IO 多路複用解決的問題


阻塞與非阻塞

解決接收數據前的耗時問題

  1. 客戶端向服務端發起請求,建立連接

  2. 雙方會收發數據,但是如果一方沒有成功發送數據,另一方的線程就會阻塞。

  3. 採用 select() 等多路複用模式,集中到一個線程去監聽多個連接上對方的數據是否準備完畢,

  4. 如果 select 收到對方線程準備發送數據的信號,就會通知用戶進程調用 recv 方法去接收連接上輸送的數據

  5. recv 本身也可長期監聽連接對方的數據是否有發送,但是 recv 的資源開銷比較大隻能一對一監聽,select 多路複用開銷小,可以一對多監聽

同步與異步

解決接收數據時的耗時問題

IO 多路複用可以在一定程度上解決阻塞 IO 和同步 IO 的問題。通過使用 IO 多路複用,程序可以同時監視多個 IO 流,而不會阻塞在單個 IO 操作上。當某個 IO 流就緒時,程序可以立即進行處理,而不需要等待其他 IO 操作的完成。通過將 IO 流設置爲非阻塞模式,程序可以立即返回並繼續執行其他任務,而不必等待 IO 操作的完成。

多路複用的優點


多路複用可以在一個連接上同時處理多個請求響應,這樣可以大大的減少連接的數量,並提高了網絡的處理能力。

由於是共享連接不同請求響應數據包可以合併到一個 IO 上處理,這樣可以大大降低 IO 的處理量,讓性能表現得更出色。

多路複用的基本原則,就是儘量減少因 IO 讀取而造成的頻繁系統調用。所謂多路複用,是指通過一次系統調用,獲得 IO 狀態,獲取到 IO 狀態之後,由 APP 自己對符合狀態的 IO 進行讀寫操作。

無論是 BIO,NIO 還是多路複用,都是同步 IO 模型,其中 BIO 是同步阻塞模型,NIO 和多路複用是同步非阻塞模型。

補充:

redis 的網咯 IO


Redis 使用 epoll 作爲 I/O 多路複用技術的實現;Redis 自身的事件處理模型將 epoll 中的連接、讀寫、關閉都轉爲時間,從而避免了在網絡 I/O 上時間的浪費。

避免了線程切換和競態產生的消耗。

影響 Redis 耗時的操作


  1. 對 bigkey 的寫入刪除操作;

  2. sql 語句複雜(Redis 使用的是同步 IO,高併發時此處會阻塞主線程,6.0 後使用多線程機制執行 sql 語句);

  3. 同時刪除大量 key;

  4. AOF 設置爲高頻率寫入磁盤;

  5. 不合理的 key 淘汰機制(內存滿了後要先刪除部分舊的 key 才能寫入新的 key,高版本 Redis 優化後採用了異步線程來執行淘汰)。

Redis 6.0 多線程模型思想上類似單 reactor 多線程和多 reactor 多線程,但不完全一樣,這兩者 handler 對於邏輯處理這一塊都是使用線程池,而 redis 命令執行依舊保持單線程。如下:

Redis 的優化


Redis 基於的底層 I/O 多路複用庫有多套。包括 select、epoll、evport 和 kqueue 等。

每個 IO 多路複用函數庫在 Redis 源碼中都對應一個單獨的文件,比如 ae_select.c,ae_epoll.c, ae_kqueue.c 等。調用 epoll 機制,讓內核監聽這些套接字。Redis 線程不會阻塞在某一個特定的監聽或已連接套接字上,也就是說,不會阻塞在某一個特定的客戶端請求處理上。

正因爲此,Redis 可以同時和多個客戶端連接並處理請求,從而提升併發性。

補充:

BIO、NIO、AIO 適用場景分析:

BIO 方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4 以前的唯一選擇,但程序直觀簡單易理解。

NIO 方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4 開始支持。

AIO 方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用 OS 參與併發操作,編程比較複雜,JDK7 開始支持。

我是程序員小於哥

@終端研發部

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/hiIFdn_-zVe0KZ2EWM80ZQ