0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

探究Redis網(wǎng)絡(luò)模型究竟有多強(qiáng)大(下)

jf_78858299 ? 來源:蟬沐風(fēng)的碼場 ? 作者:蟬沐風(fēng) ? 2023-03-03 09:50 ? 次閱讀

4. 非阻塞I/O(NonBlocking I/O)

上文花了太多的筆墨描述BIO,接下來的非阻塞IO我們只抓主要矛盾,其余參考BIO即可。

如果你看過其他介紹非阻塞IO的文章,下面這個(gè)圖片你多少會(huì)有點(diǎn)眼熟。

圖片

NIO模型

非阻塞IO指的是進(jìn)程發(fā)起系統(tǒng)調(diào)用之后,內(nèi)核不會(huì)將進(jìn)程投入睡眠,而是會(huì)立即返回一個(gè)結(jié)果,這個(gè)結(jié)果可能恰好是我們需要的數(shù)據(jù),又或者是某些錯(cuò)誤。

你可能會(huì)想,這種非阻塞帶來的輪詢有什么用呢?大多數(shù)都是空輪詢,白白浪費(fèi)CPU而已,還不如讓進(jìn)程休眠來的合適。

4.1 Java的非阻塞實(shí)現(xiàn)

這個(gè)問題暫且擱置一下,我們先看Java在語法層面是如何提供非阻塞功能的,細(xì)節(jié)慢慢聊。

public class NoBlockingServer {

    public static List<SocketChannel> channelList = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {

        try {
            // 相當(dāng)于serverSocket
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 將監(jiān)聽socket設(shè)置為非阻塞
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(8099));
            while (true) {
                // 這里將不再阻塞
                SocketChannel socketChannel = serverSocketChannel.accept();

                if (socketChannel != null) {
                    // 將連接socket設(shè)置為非阻塞
                    socketChannel.configureBlocking(false);
                    channelList.add(socketChannel);
                } else {
                    System.out.println("沒有客戶端連接!!!");
                }

                for (SocketChannel client : channelList) {
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    // read也不阻塞
                    int num = client.read(byteBuffer);
                    if (num > 0) {
                        System.out.println("收到客戶端【" + client.socket().getPort() + "】數(shù)據(jù):" + new String(byteBuffer.array()));
                    } else {
                        System.out.println("等待客戶端【" + client.socket().getPort() + "】寫數(shù)據(jù)");
                    }
                }

                // 加個(gè)睡眠是為了避免strace產(chǎn)生大量日志,否則不好追蹤
                Thread.sleep(1000);

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Java提供了新的APIServerSocketChannel以及SocketChannel,相當(dāng)于BIO中的ServerSocketSocket。此外,通過下面兩行的配置,將監(jiān)聽socket和連接socket設(shè)置為非阻塞。

// 將監(jiān)聽socket設(shè)置為非阻塞
serverSocketChannel.configureBlocking(false);

// 將連接socket設(shè)置為非阻塞
socketChannel.configureBlocking(false);

我們上文強(qiáng)調(diào)過, Java自身并沒有將socket設(shè)置為非阻塞的本事,一定是在某個(gè)時(shí)間點(diǎn)上,操作系統(tǒng)內(nèi)核提供了這個(gè)功能,才使得Java設(shè)計(jì)出了新的API來提供非阻塞功能 。

之所以需要上面兩行代碼的顯式設(shè)置,也恰好說明了內(nèi)核是默認(rèn)將socket設(shè)置為阻塞狀態(tài)的,需要非阻塞,就得額外調(diào)用其他系統(tǒng)調(diào)用。我們通過man命令查看一下socket()這個(gè)方法(截圖的中間省略了一部分內(nèi)容):

man 2 socket

f336ca741589b768db4fecb030b6b432.png

image-20221225144028751

我們可以看到socket()函數(shù)提供了SOCK_NONBLOCK這個(gè)類型,可以通過fcntl()這個(gè)方法將socket從默認(rèn)的阻塞修改為非阻塞,不管是對監(jiān)聽socket還是連接socket都是一樣的。

4.2 Java的非阻塞解釋

現(xiàn)在解釋上面提到的問題:這種非阻塞帶來的輪詢有什么用?觀察一下上面的代碼就可以發(fā)現(xiàn),我們?nèi)讨皇褂昧?個(gè)main線程就解決了所有客戶端的連接以及所有客戶端的讀寫操作。

serverSocketChannel.accept();會(huì)立即返回調(diào)用結(jié)果。

返回的結(jié)果如果是一個(gè)SocketChannel對象(系統(tǒng)調(diào)用底層就是個(gè)socket描述符),說明有客戶端連接,這個(gè)SocketChannel就表示了這個(gè)連接;然后利用socketChannel.configureBlocking(false);將這個(gè)連接socket設(shè)置為非阻塞。這個(gè)設(shè)置非常重要,設(shè)置之后對連接socket所有的讀寫操作都變成了非阻塞,因此接下來的client.read(byteBuffer);并不會(huì)阻塞while循環(huán),導(dǎo)致新的客戶端無法連接。再之后將該連接socket加入到channelList隊(duì)列中。

如果返回的結(jié)果為空(底層系統(tǒng)調(diào)用返回了錯(cuò)誤),就說明現(xiàn)在還沒有新的客戶端要連接監(jiān)聽socket,因此程序繼續(xù)向下執(zhí)行,遍歷channelList隊(duì)列中的所有連接socket,對連接socket進(jìn)行讀操作。而讀操作也是非阻塞的,會(huì)理解返回一個(gè)整數(shù),表示讀到的字節(jié)數(shù),如果>0,則繼續(xù)進(jìn)行下一步的邏輯處理;否則繼續(xù)遍歷下一個(gè)連接socket。

下面給出一張accept()返回一個(gè)連接socket情況下的動(dòng)圖,希望對大家理解整個(gè)流程有幫助。

4.3 掀開非阻塞IO的底褲

我將上面的程序在CentOS下再次用strace程序追蹤一下,具體步驟不再贅述,下面是out日志文件的內(nèi)容(我忽略了絕大多數(shù)沒用的)。

圖片

非阻塞IO的系統(tǒng)調(diào)用分析

4.4 非阻塞IO總結(jié)

圖片

NIO模型

再放一遍這個(gè)圖,有一個(gè)細(xì)節(jié)需要大家注意,系統(tǒng)調(diào)用向內(nèi)核要數(shù)據(jù)時(shí),內(nèi)核的動(dòng)作分成兩步:

  1. 等待數(shù)據(jù)(從網(wǎng)卡緩沖區(qū)拷貝到內(nèi)核緩沖區(qū))
  2. 拷貝數(shù)據(jù)(數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶空間)

只有在第1步時(shí),系統(tǒng)調(diào)用是非阻塞的,第2步進(jìn)程依然需要等待這個(gè)拷貝過程,然后才能返回,這一步是阻塞的。

非阻塞IO模型僅用一個(gè)線程就能處理所有操作,對比BIO的一個(gè)客戶端需要一個(gè)線程而言進(jìn)步還是巨大的。但是他的致命問題在于會(huì)不停地進(jìn)行系統(tǒng)調(diào)用,不停的進(jìn)行accept(),不停地對連接socket進(jìn)行read()操作,即使大部分時(shí)間都是白忙活。要知道,系統(tǒng)調(diào)用涉及到用戶空間和內(nèi)核空間的多次轉(zhuǎn)換,會(huì)嚴(yán)重影響整體性能。

所以,一個(gè)自然而言的想法就是,能不能別讓進(jìn)程瞎輪詢。

比如有人告訴進(jìn)程監(jiān)聽socket是不是被連接了,有的話進(jìn)程再執(zhí)行accept();比如有人告訴進(jìn)程哪些連接socket有數(shù)據(jù)從客戶端發(fā)送過來了,然后進(jìn)程只對有數(shù)據(jù)的連接socket進(jìn)行read()

這個(gè)方案就是 I/O多路復(fù)用

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • IO
    IO
    +關(guān)注

    關(guān)注

    0

    文章

    434

    瀏覽量

    39051
  • 非阻塞
    +關(guān)注

    關(guān)注

    0

    文章

    13

    瀏覽量

    2164
  • Redis
    +關(guān)注

    關(guān)注

    0

    文章

    370

    瀏覽量

    10830
收藏 人收藏

    評論

    相關(guān)推薦

    小米5的那顆核心,究竟有多強(qiáng)

    小米5的那顆核心,究竟有多強(qiáng)自從小米在2011年崛起后,高通的驍龍系列處理器就逐漸成為了市面上旗艦手機(jī)的主流處理器,從蝎子核心到環(huán)蛇核心,再到現(xiàn)在的驍龍810,高通一直在進(jìn)步。那么,這款驍龍810
    發(fā)表于 06-01 19:35

    Redis Stream應(yīng)用案例

    今天介紹的主角——Redis Stream,本身就是起源于IRC中一個(gè)用戶的idea。IRC的模型如下,在某個(gè)IRC頻道中的用戶,既可以向所有的其他用戶自由的發(fā)送消息,也可以接收其他所有用戶發(fā)送
    發(fā)表于 06-26 17:15

    液晶PC與液晶電視究竟有什么區(qū)別?

    為什么要選擇液晶?液晶PC與液晶電視究竟有什么區(qū)別?如何選擇液晶PC與液晶電視?
    發(fā)表于 06-07 06:13

    請問一RFID與NFC究竟有什么關(guān)系?

    RFID與NFC究竟有什么關(guān)系?
    發(fā)表于 06-15 07:06

    面向列的HBase存儲(chǔ)結(jié)構(gòu)究竟有什么樣的不同之處呢?

    HBase是什么?HBase的存儲(chǔ)結(jié)構(gòu)究竟是怎樣的呢?面向列的HBase存儲(chǔ)結(jié)構(gòu)究竟有什么樣的不同之處呢?
    發(fā)表于 06-16 06:52

    請問一芯片制造究竟有多難?

    請問一芯片制造究竟有多難?
    發(fā)表于 06-18 06:53

    PCI-E4.0究竟有什么優(yōu)勢?

    PCI-E4.0究竟有什么優(yōu)勢?PCI-E究竟指的是什么呢?
    發(fā)表于 06-18 06:54

    內(nèi)存時(shí)序究竟有多重要呢?究竟該如何去選擇內(nèi)存條呢?

    內(nèi)存時(shí)序究竟有多重要呢?究竟該如何去選擇內(nèi)存條呢?DDR內(nèi)存時(shí)序是高一些好還是低一些好?
    發(fā)表于 06-18 08:20

    定時(shí)器中斷類型探究 精選資料分享

     一直在用的stm32定時(shí)器的中斷都是TIM_IT_Update更新中斷,也沒問為什么,直到碰到有人使用TIM_IT_CC1中斷,才想到這定時(shí)器的中斷類型究竟有什么區(qū)別,都怪當(dāng)時(shí)學(xué)習(xí)stm32的時(shí)候
    發(fā)表于 08-13 06:28

    OpenPLC開源工業(yè)控制器究竟有何用處

    OpenPLC開源工業(yè)控制器有哪些優(yōu)點(diǎn)?OpenPLC開源工業(yè)控制器有哪些功能?OpenPLC開源工業(yè)控制器究竟有何用處?
    發(fā)表于 09-02 07:42

    華為榮耀Magic今日發(fā)布:“未來”手機(jī)究竟有多強(qiáng)?

    華為榮耀即將在12月16日發(fā)布最新的“未來”手機(jī)magic,關(guān)于這款手機(jī)的爆料在今日已經(jīng)鋪天蓋地,今天,小編將為大家整理一,給大家一個(gè)榮耀Magic的基本判斷,看看這款旗艦究竟有多強(qiáng)力!
    發(fā)表于 12-16 09:34 ?3313次閱讀

    ibm的2nm芯片究竟有多強(qiáng) 2nm芯片對續(xù)航的影響

    全球首顆2nm芯片的問世對半導(dǎo)體行業(yè)影響重大,IBM通過與AMD、三星及GlobalFoundries等多家公司的合作,最終抵達(dá)了2nm芯片制程的節(jié)點(diǎn),推出了2nm的測試芯片。那么這顆芯片究竟有多強(qiáng)呢?它對續(xù)航的影響又有多大呢?
    的頭像 發(fā)表于 06-23 09:35 ?2053次閱讀

    Molex莫仕連接器的功能究竟有多強(qiáng)大?看他們的行業(yè)應(yīng)用你就知道了!

    KOYUELEC光與電子:Molex莫仕連接器的功能究竟有多強(qiáng)大?看他們的行業(yè)應(yīng)用你就知道了!
    的頭像 發(fā)表于 12-31 12:30 ?1w次閱讀

    探究Redis網(wǎng)絡(luò)模型究竟有多強(qiáng)大(上)

    本文將從BIO開始介紹,經(jīng)過NIO、多路復(fù)用,最終說回Redis的Reactor模型,力求詳盡。本文與其他文章的不同點(diǎn)主要在于:
    的頭像 發(fā)表于 03-03 09:46 ?423次閱讀
    <b class='flag-5'>探究</b><b class='flag-5'>Redis</b><b class='flag-5'>網(wǎng)絡(luò)</b><b class='flag-5'>模型</b><b class='flag-5'>究竟有多強(qiáng)大</b>(上)

    探究Redis網(wǎng)絡(luò)模型究竟有多強(qiáng)大(中)

    創(chuàng)建socket這一步和客戶端沒啥區(qū)別,不同的是這個(gè)socket我們稱之為 **等待連接socket(或監(jiān)聽socket)** 。 #### 3.2.2 綁定端口號 `bind()`函數(shù)會(huì)將端口號寫入上
    的頭像 發(fā)表于 03-03 09:49 ?329次閱讀
    <b class='flag-5'>探究</b><b class='flag-5'>Redis</b><b class='flag-5'>網(wǎng)絡(luò)</b><b class='flag-5'>模型</b><b class='flag-5'>究竟有多強(qiáng)大</b>(中)