這種本機(jī)網(wǎng)絡(luò) IO 方法,性能可以翻倍!
原文地址:https://mp.weixin.qq.com/s/fHzKYlW0WMhP2jxh2H_59A
大家好,我是飛哥!
很多讀者在看完《127.0.0.1 之本機(jī)網(wǎng)絡(luò)通信過程知多少 ?》這一篇后,讓我講講 Unix Domain Socket。好了,今天就安排!
在本機(jī)網(wǎng)絡(luò) IO 中,我們講到過基于普通 socket 的本機(jī)網(wǎng)絡(luò)通信過程中,其實(shí)在內(nèi)核工作流上并沒有節(jié)約太多的開銷。該走的系統(tǒng)調(diào)用、協(xié)議棧、鄰居系統(tǒng)、設(shè)備驅(qū)動(dòng)(雖然說對(duì)于本機(jī)網(wǎng)絡(luò) loopback 設(shè)備來說只是一個(gè)軟件虛擬的東東)全都走了一遍。其工作過程如下圖

那么我們今天來看另外一種本機(jī)網(wǎng)絡(luò) IO 通信方式 -- Unix Domain Socket??纯催@種方式在性能開銷上和基于 127.0.0.1 的本機(jī)網(wǎng)絡(luò) IO 有沒有啥差異呢。
本文中,我們將分析 Unix Domain Socket 的內(nèi)部工作原理。你將理解為什么這種方式的性能比 127.0.0.1 要好很多。最后我們還給出了實(shí)際的性能測(cè)試對(duì)比數(shù)據(jù)。
相信你已經(jīng)迫不及待了,別著急,讓我們一一展開細(xì)說!
一、使用方法
Unix Domain Socket(后面統(tǒng)一簡(jiǎn)稱 UDS) 使用起來和傳統(tǒng)的 socket 非常的相似。區(qū)別點(diǎn)主要有兩個(gè)地方需要關(guān)注。
第一,在創(chuàng)建 socket 的時(shí)候,普通的 socket 第一個(gè)參數(shù) family 為 AF_INET, 而 UDS 指定為 AF_UNIX 即可。
第二,Server 的標(biāo)識(shí)不再是 ip 和 端口,而是一個(gè)路徑,例如 /dev/shm/fpm-cgi.sock。
其實(shí)在平時(shí)我們使用 UDS 并不一定需要去寫一段代碼,很多應(yīng)用程序都支持在本機(jī)網(wǎng)絡(luò) IO 的時(shí)候配置。例如在 Nginx 中,如果要訪問的本機(jī) fastcgi 服務(wù)是以 UDS 方式提供服務(wù)的話,只需要在配置文件中配置這么一行就搞定了。
如果 對(duì)于一個(gè) UDS 的 server 來說,它的代碼示例大概結(jié)構(gòu)如下,大家簡(jiǎn)單了解一下。只是個(gè)示例不一定可運(yùn)行。
基于 UDS 的 client 也是和普通 socket 使用方式差不太多,創(chuàng)建一個(gè) socket,然后 connect 即可。
二、連接過程
總的來說,基于 UDS 的連接過程比 inet 的 socket 連接過程要簡(jiǎn)單多了??蛻舳讼葎?chuàng)建一個(gè)自己用的 socket,然后調(diào)用 connect 來和服務(wù)器建立連接。
在 connect 的時(shí)候,會(huì)申請(qǐng)一個(gè)新 socket 給 server 端將來使用,和自己的 socket 建立好連接關(guān)系以后,就放到服務(wù)器正在監(jiān)聽的 socket 的接收隊(duì)列中。這個(gè)時(shí)候,服務(wù)器端通過 accept 就能獲取到和客戶端配好對(duì)的新 socket 了。
總的 UDS 的連接建立流程如下圖。

內(nèi)核源碼中最重要的邏輯在 connect 函數(shù)中,我們來簡(jiǎn)單展開看一下。unix 協(xié)議族中定義了這類 socket 的所有方法,它位于 net/unix/af_unix.c 中。
我們找到 connect 函數(shù)的具體實(shí)現(xiàn),unix_stream_connect。
主要的連接操作都是在這個(gè)函數(shù)中完成的。和我們平常所見的 TCP 連接建立過程,這個(gè)連接過程簡(jiǎn)直是太簡(jiǎn)單了。沒有三次握手,也沒有全連接隊(duì)列、半連接隊(duì)列,更沒有啥超時(shí)重傳。
直接就是將兩個(gè) socket 結(jié)構(gòu)體中的指針互相指向?qū)Ψ骄托辛恕>褪?unix_peer(newsk) = sk 和?unix_peer(sk)?= newsk 這兩句。
當(dāng)關(guān)聯(lián)關(guān)系建立好之后,通過 __skb_queue_tail 將 skb 放到服務(wù)器的接收隊(duì)列中。注意這里的 skb 里保存著新 socket 的指針,因?yàn)榉?wù)進(jìn)程通過 accept 取出這個(gè) skb 的時(shí)候,就能獲取到和客戶進(jìn)程中 socket 建立好連接關(guān)系的另一個(gè) socket。
怎么樣,UDS 的連接建立過程是不是很簡(jiǎn)單!?
三、發(fā)送過程
看完了連接建立過程,我們?cè)賮砜纯椿?UDS 的數(shù)據(jù)的收發(fā)。這個(gè)收發(fā)過程一樣也是非常的簡(jiǎn)單。發(fā)送方是直接將數(shù)據(jù)寫到接收方的接收隊(duì)列里的。

我們從 send 函數(shù)來看起。send 系統(tǒng)調(diào)用的源碼位于文件 net/socket.c 中。在這個(gè)系統(tǒng)調(diào)用里,內(nèi)部其實(shí)真正使用的是 sendto 系統(tǒng)調(diào)用。它只干了兩件簡(jiǎn)單的事情,
第一是在內(nèi)核中把真正的 socket 找出來,在這個(gè)對(duì)象里記錄著各種協(xié)議棧的函數(shù)地址。第二是構(gòu)造一個(gè) struct msghdr 對(duì)象,把用戶傳入的數(shù)據(jù),比如 buffer地址、數(shù)據(jù)長度啥的,統(tǒng)統(tǒng)都裝進(jìn)去. 剩下的事情就交給下一層,協(xié)議棧里的函數(shù) inet_sendmsg 了,其中 inet_sendmsg 函數(shù)的地址是通過 socket 內(nèi)核對(duì)象里的 ops 成員找到的。大致流程如圖。

在進(jìn)入到協(xié)議棧 inet_sendmsg 以后,內(nèi)核接著會(huì)找到 socket 上的具體協(xié)議發(fā)送函數(shù)。對(duì)于 Unix Domain Socket 來說,那就是 unix_stream_sendmsg。我們來看一下這個(gè)函數(shù)
和復(fù)雜的 TCP 發(fā)送接收過程相比,這里的發(fā)送邏輯簡(jiǎn)單簡(jiǎn)單到令人發(fā)指。申請(qǐng)一塊內(nèi)存(skb),把數(shù)據(jù)拷貝進(jìn)去。根據(jù) socket 對(duì)象找到另一端,直接把 skb 給放到對(duì)端的接收隊(duì)列里了
接收函數(shù)主題是 unix_stream_recvmsg,這個(gè)函數(shù)中只需要訪問它自己的接收隊(duì)列就行了,源碼就不展示了。所以在本機(jī)網(wǎng)絡(luò) IO 場(chǎng)景里,基于 Unix Domain Socket 的服務(wù)性能上肯定要好一些的。
四、性能對(duì)比
為了驗(yàn)證 Unix Domain Socket 到底比基于 127.0.0.1 的性能好多少,我做了一個(gè)性能測(cè)試。在網(wǎng)絡(luò)性能對(duì)比測(cè)試,最重要的兩個(gè)指標(biāo)是延遲和吞吐。我從 Github 上找了個(gè)好用的測(cè)試源碼:https://github.com/rigtorp/ipc-bench。我的測(cè)試環(huán)境是一臺(tái) 4 核 CPU,8G 內(nèi)存的 KVM 虛機(jī)。
在延遲指標(biāo)上,對(duì)比結(jié)果如下圖。

可見在小包(100 字節(jié))的情況下,UDS 方法的“網(wǎng)絡(luò)” IO 平均延遲只有 2707 納秒,而基于 TCP(訪問 127.0.0.1)的方式下延遲高達(dá) 5690 納秒。耗時(shí)整整是前者的兩倍。
在包體達(dá)到 100 KB 以后,UDS 方法延遲 24 微秒左右(1 微秒等于 1000 納秒),TCP 是 32 微秒,仍然高一截。這里低于 2 倍的關(guān)系了,是因?yàn)楫?dāng)包足夠大的時(shí)候,網(wǎng)絡(luò)協(xié)議棧上的開銷就顯得沒那么明顯了。
再來看看吞吐效果對(duì)比。

在小包的情況下,帶寬指標(biāo)可以達(dá)到 854 M,而基于 TCP 的 IO 方式下只有 386 M。數(shù)據(jù)就解讀到這里。
五、總結(jié)
本文分析了基于 Unix Domain Socket 的連接創(chuàng)建、以及數(shù)據(jù)收發(fā)過程。其中數(shù)據(jù)收發(fā)的工作過程如下圖。

相對(duì)比本機(jī)網(wǎng)絡(luò) IO 通信過程上,它的工作過程要清爽許多。其中 127.0.0.1 工作過程如下圖。

我們也對(duì)比了 UDP 和 TCP 兩種方式下的延遲和性能指標(biāo)。在包體不大于 1KB 的時(shí)候,UDS 的性能大約是 TCP 的兩倍多。所以,在本機(jī)網(wǎng)絡(luò) IO 的場(chǎng)景下,如果對(duì)性能敏感,飛哥建議你使用 Unix Domain Socket。
