io_uring,幹翻 nio!

張哥說技術發表於2023-02-08

io_uring,幹翻 nio!

原創:小姐姐味道(微信公眾號ID:xjjdog),歡迎分享,非公眾號轉載保留此宣告。

大家都知道BIO非常的低效,而網路程式設計中的IO多路複用普遍比較高效。

現在,io_uring已經能夠挑戰NIO的,功能非常強大。io_uring在2019加入了Linux核心,目前5.1+的核心,可以採用這個功能。

隨著一步步的最佳化,系統呼叫這個大傢伙,呼叫次數越來越少了。

一、效能耗費在哪裡?

在Linux的效能指標裡,有ussy兩個指標,使用top命令可以很方便的看到。

io_uring,幹翻 nio!

us是使用者程式的意思,而sy是在核心中所使用的cpu佔比。如果程式在核心態和使用者態切換的非常頻繁,那麼效率大部分就會浪費在切換之上。

一次核心態和使用者態切換的時間,普遍在微秒級別以上,可以說非常昂貴了。

cpu的效能是固定的,在無用的東西上浪費越小,在真正業務上的處理就效率越高。影響效率的有兩個方面。

  1. 程式或者執行緒的數量,引起過多的上下文切換。程式是由核心來管理和排程的,程式的切換隻能發生在核心態。所以,如果你的程式碼切換了執行緒,它必然伴隨著一次使用者態和核心態的切換。
  2. IO的程式設計模型,引起過多的系統態和核心態切換。比如同步阻塞等待的模型,需要經過資料接收、軟中斷的處理(核心態),然後喚醒使用者執行緒(使用者態),處理完畢之後再進入等待狀態(核心態)。

關於mmap,可以參考這篇文章。

《OS近距離:mmap給你想要的快!》

二、BIO

可以說,BIO這種模式,線上程數量上爆炸,程式設計模型古老,把效能低的原因全給佔了。

通常情況下,BIO一條連線就對應著一個執行緒。BIO的讀寫操作是阻塞的,執行緒的整個生命週期和連線的生命週期是一樣的,而且不能夠被複用。

如果連線有1000條,那就需要1000個執行緒。執行緒資源是非常昂貴的,除了佔用大量的記憶體,還會佔用非常多的CPU排程時間,所以BIO在連線非常多的情況下,效率會變得非常低。

BIO的程式設計模型,也存在諸多缺陷。因為它是阻塞性程式設計模式,在有資料的時候,需要核心通知它;在沒有資料的時候,需要阻塞wait在相應的socket上。這兩個操作,都涉及到核心態和使用者態的切換。如果資料包文非常頻繁,BIO就需要這麼一直切換。

三、NIO

提到NIO,Java中使用的是Epoll,Netty使用的是改良後的Epoll,它們都是多路複用,只不過叫慣了,所以稱作NIO。

採用Reactor程式設計模型,可以採用非常少的執行緒,就能夠應對海量的Socket連線。

一旦有新的事件到達,比如有新的連線到來,主執行緒就能夠被排程到,程式就能夠向下執行。這時候,就能夠根據訂閱的事件通知,持續獲取訂閱的事件。

NIO是基於事件機制的,有一個叫做Selector的選擇器,阻塞獲取關注的事件列表。獲取到事件列表後,可以透過分發器,進行真正的資料操作。

io_uring,幹翻 nio!

熟悉Netty的同學可以看到,這個模型就是Netty設計的基礎。在Netty中,Boss執行緒對應著對連線的處理和分派,相當於mainReactor;Work執行緒 對應著subReactor,使用多執行緒負責讀寫事件的分發和處理。

透過Selector選擇器,NIO將BIO中頻繁的wait和notify操作,集中在了一起,大量的減少了核心態和使用者態的切換。在網路流量比較高的時候,Selector甚至都不會阻塞,它將一直處於處理資料的過程中。

這種模式將每個元件的職責分的更細,耦合度也更低,能有效的解決C10k問題。

四、io_uring

但是,NIO依然有大量的系統呼叫,那就是Epoll的epoll_ctl。另外,獲取到網路事件之後,還需要把socket的資料進行存取,這也是一次系統呼叫。雖然相對於BIO來說,上下文切換次數已經減少很多,但它仍然花費了比較多的時間在切換之上。

IO只負責對發生在fd描述符上的事件進行通知。事件的獲取和通知部分是非阻塞的,但收到通知之後的操作,卻是阻塞的。即使使用多執行緒去處理這些事件,它依然是阻塞的。

如果能把這些系統呼叫都放在作業系統裡完成,那麼就可以節省下這些系統呼叫的時間,io_uring就是幹這個的。

io_uring,幹翻 nio!

如圖,使用者態和核心態共享提交佇列(submission queue)和完成佇列(completion queue),這兩條佇列透過mmap共享,高效且安全。

(SQ)給核心源源不斷的佈置任務,然後從另外一條佇列(CQ)獲取結果;核心則按需進行 epoll(),並在一個執行緒池中執行就緒的任務。

使用者態支援Polling模式,不會發生中斷,也就沒有系統呼叫,透過輪詢即可消費事件;核心態也支援Polling模式,同樣不會發生上下文切換。

可以看出關鍵的設計在於,核心透過一塊和使用者共享的記憶體區域進行訊息的傳遞,可以繞過Linux 的 syscall 機制。

rocksdb、ceph等應用,已經在嘗試這些功能,隨著核心io_uring的成熟,相信網路程式設計在效率上會更上一層樓。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2934374/,如需轉載,請註明出處,否則將追究法律責任。