最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis IO多路復(fù)用模型使用及說(shuō)明

 更新時(shí)間:2026年03月24日 09:54:35   作者:我學(xué)學(xué)學(xué)學(xué)學(xué)學(xué)學(xué)  
這篇文章主要介紹了Redis IO多路復(fù)用模型使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

Redis單線程是如何處理那么多并發(fā)客戶端連接?為什么單線?為什么那么快?

這是因?yàn)镽edis使用了IO多路復(fù)用,Redis利用epoll來(lái)實(shí)現(xiàn)IO多路復(fù)用,將連接信息和事件放到隊(duì)列中,一次放到文件事件分派器,事件分派器將事件分發(fā)給事件處理器。

我們一般面試中都會(huì)這樣回答,那么什么是IO多路復(fù)用模型?epoll又是什么?首先我們先來(lái)了解一下UNIX網(wǎng)絡(luò)編程的五種IO模型。

UNIX網(wǎng)絡(luò)編程的五種IO模型

1、Blocking IO:阻塞IO;

2、NoneBlocking IO: 非阻塞IO;

3、IO multiplexing:IO多路復(fù)用;

4、signal driven IO:信號(hào)驅(qū)動(dòng)IO(略);

5、asynchronous IO:異步IO(略)。

同步和異步

同步:調(diào)用者要一直等待調(diào)用結(jié)果的通知后才能進(jìn)行后續(xù)的執(zhí)行?,F(xiàn)在就要,我可以等,等到結(jié)果為止。

異步:指被調(diào)用方先返回應(yīng)答讓調(diào)用者先回去,然后再計(jì)算調(diào)用結(jié)果,計(jì)算完最終結(jié)果再通知并返回給調(diào)用方。異步調(diào)用想要獲取結(jié)果一般通過(guò)回調(diào)。

同步、異步的討論對(duì)象是被調(diào)用者(服務(wù)提供者),重點(diǎn)在于獲得調(diào)用結(jié)果的消息通知方式上。

阻塞和非阻塞

阻塞:調(diào)用方一直在等待且別的事情什么都不做,當(dāng)前線 / 進(jìn)程會(huì)被掛起,啥也不干。

非阻塞:調(diào)用在發(fā)出去后,調(diào)用方先去做別的事情,不會(huì)阻塞當(dāng)前線 / 進(jìn)程,而會(huì)立刻返回。

阻塞和非阻塞討論的對(duì)象是調(diào)用者(服務(wù)請(qǐng)求者),重點(diǎn)在于等消息時(shí)候的行為,調(diào)用者是否能干其他事。

4種組合方式

同步阻塞、同步非阻塞、異步阻塞、異步非阻塞。

同步阻塞:服務(wù)員說(shuō)快到你了,先別離開(kāi)我后臺(tái)看一眼馬上通知你??蛻粼诤5讚魄芭_(tái)干等,啥也不干。

同步非阻塞:服務(wù)員說(shuō)快到你了,先別離開(kāi)??蛻粼诤5讚魄芭_(tái)邊刷抖音邊等叫號(hào)。

異步阻塞:服務(wù)員說(shuō)還需要等位,你先去逛逛,一會(huì)通知你。客戶怕過(guò)號(hào)在海底撈前臺(tái)拿著排號(hào)小票啥也不干,就在那等著店員通知。

異步非阻塞:服務(wù)員說(shuō)還需要等位,你先去逛逛,一會(huì)通知你。拿著排號(hào)小票,刷著抖音,等著店員通知。

BIO

在阻塞式I/O模型中,應(yīng)用程序在從調(diào)用rercvfrom開(kāi)始到他返回有數(shù)據(jù)報(bào)準(zhǔn)備好這段時(shí)間是阻塞的,recvfrom返回成功后,應(yīng)用進(jìn)程才能開(kāi)始處理數(shù)據(jù)報(bào)。tomcat7之前就是用BIO多線程來(lái)解決多連接。

accept監(jiān)聽(tīng)

public class RedisClient01
{
    public static void main(String[] args) throws IOException
    {
        System.out.println("------RedisClient01 start");
        Socket socket = new Socket("127.0.0.1", 6379);
        System.out.println("------RedisClient01 connection over");
    }
}
public class RedisClient02
{
    public static void main(String[] args) throws IOException
    {
        System.out.println("------RedisClient02 start");
        Socket socket = new Socket("127.0.0.1", 6379);
        System.out.println("------RedisClient02 connection over");
    }
}
public class RedisServer
{
    public static void main(String[] args) throws IOException
    {
        ServerSocket serverSocket = new ServerSocket(6379);

        while(true)
        {
            System.out.println("模擬RedisServer啟動(dòng)-----111 等待連接");
            Socket socket = serverSocket.accept();
            System.out.println("-----222 成功連接:  "+ IdUtil.simpleUUID());
            System.out.println();
        }
    }
}

啟動(dòng)RedisServer,并啟動(dòng)RedisClient01,RedisClient02。

read讀取

public class RedisClient01
{
    public static void main(String[] args) throws IOException
    {
        Socket socket = new Socket("127.0.0.1",6379);
        OutputStream outputStream = socket.getOutputStream();

        while(true)
        {
            Scanner scanner = new Scanner(System.in);
            String string = scanner.next();
            if (string.equalsIgnoreCase("quit")) {
                break;
            }
            socket.getOutputStream().write(string.getBytes());
            System.out.println("------RedisClient01 input quit keyword to finish......");
        }
        outputStream.close();
        socket.close();
    }
}
public class RedisClient02
{
    public static void main(String[] args) throws IOException
    {
        Socket socket = new Socket("127.0.0.1",6379);
        OutputStream outputStream = socket.getOutputStream();

        while(true)
        {
            Scanner scanner = new Scanner(System.in);
            String string = scanner.next();
            if (string.equalsIgnoreCase("quit")) {
                break;
            }
            socket.getOutputStream().write(string.getBytes());
            System.out.println("------RedisClient02 input quit keyword to finish......");
        }
        outputStream.close();
        socket.close();
    }
}
public class RedisServerBIO
{
    public static void main(String[] args) throws IOException
    {
        ServerSocket serverSocket = new ServerSocket(6379);

        while(true)
        {
            System.out.println("-----111 等待連接");
            Socket socket = serverSocket.accept();//阻塞1 ,等待客戶端連接
            System.out.println("-----222 成功連接");

            InputStream inputStream = socket.getInputStream();
            int length = -1;
            byte[] bytes = new byte[1024];
            System.out.println("-----333 等待讀取");
            while((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客戶端發(fā)送數(shù)據(jù)
            {
                System.out.println("-----444 成功讀取"+new String(bytes,0,length));
                System.out.println("===================="+"\t"+ IdUtil.simpleUUID());
                System.out.println();
            }
            inputStream.close();
            socket.close();
        }
    }
}

啟動(dòng)RedisServerBIO,并啟動(dòng)RedisClient01發(fā)送消息。

成功讀取1號(hào)連接發(fā)送的消息,啟動(dòng)RedisClient02發(fā)送消息。

發(fā)現(xiàn)并沒(méi)有接收到2號(hào)連接發(fā)送的消息,這時(shí)我們退出1號(hào)連接。

成功接收到2號(hào)連接發(fā)送的消息。

上面的模型存在很大的問(wèn)題,如果客戶端與服務(wù)端建立了連接,如果這個(gè)連接的客戶端遲遲不發(fā)數(shù)據(jù),程就會(huì)一直堵塞在read()方法上,這樣其他客戶端也不能進(jìn)行連接,也就是一次只能處理一個(gè)客戶端,對(duì)客戶很不友好。

可以利用多線程。只要連接了一個(gè)socket,操作系統(tǒng)分配一個(gè)線程來(lái)處理,這樣read()方法堵塞在每個(gè)具體線程上而不堵塞主線程,就能操作多個(gè)socket了,哪個(gè)線程中的socket有數(shù)據(jù),就讀那個(gè)。socket,各取所需,靈活統(tǒng)一。程序服務(wù)端只負(fù)責(zé)監(jiān)聽(tīng)是否有客戶端連接,使用 accept() 阻塞??蛻舳?連接服務(wù)端,就開(kāi)辟一個(gè)線程(thread1)來(lái)執(zhí)行 read() 方法,程序服務(wù)端繼續(xù)監(jiān)聽(tīng)??蛻舳?連接服務(wù)端,也開(kāi)辟一個(gè)線程(thread2)來(lái)執(zhí)行 read() 方法,程序服務(wù)端繼續(xù)監(jiān)聽(tīng)??蛻舳?連接服務(wù)端,也開(kāi)辟一個(gè)線程(thread3)來(lái)執(zhí)行 read() 方法,程序服務(wù)端繼續(xù)監(jiān)聽(tīng)任何一個(gè)線程上的socket有數(shù)據(jù)發(fā)送過(guò)來(lái),read()就能立馬讀到,cpu就能進(jìn)行處理。

改造代碼:

public class RedisServerBIOMultiThread
{
    public static void main(String[] args) throws IOException
    {
        ServerSocket serverSocket = new ServerSocket(6379);

        while(true)
        {
            System.out.println("-----RedisServerBIOMultiThread 111 等待連接");
            Socket socket = serverSocket.accept();//阻塞1 ,等待客戶端連接
            System.out.println("-----RedisServerBIOMultiThread 222 成功連接");

            new Thread(() -> {
                try {
                    InputStream inputStream = socket.getInputStream();
                    int length = -1;
                    byte[] bytes = new byte[1024];
                    System.out.println("-----333 等待讀取"+ IdUtil.simpleUUID());
                    while((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客戶端發(fā)送數(shù)據(jù)
                    {
                        System.out.println("-----444 成功讀取"+new String(bytes,0,length));
                        System.out.println("====================");
                        System.out.println();
                    }
                    inputStream.close();
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            },Thread.currentThread().getName()).start();

            new Thread().start();

        }
    }
}

啟動(dòng)RedisServerBIOMultiThread,并啟動(dòng)RedisClient01,RedisClient02發(fā)送消息。

成功接收1號(hào)連接2號(hào)連接發(fā)送的消息,那么現(xiàn)在的BIO多線程模型還有什么問(wèn)題嗎?

多線程模型:每來(lái)一個(gè)客戶端,就要開(kāi)辟一個(gè)線程,如果來(lái)1萬(wàn)個(gè)客戶端,那就要開(kāi)辟1萬(wàn)個(gè)線程。在操作系統(tǒng)中用戶態(tài)不能直接開(kāi)辟線程,需要調(diào)用內(nèi)核來(lái)創(chuàng)建的一個(gè)線程,這其中還涉及到用戶狀態(tài)的切換(上下文的切換),十分耗資源。

解決方法:

第一個(gè)辦法:使用線程池。這個(gè)在客戶端連接少的情況下可以使用,但是用戶量大的情況下,你不知道線程池要多大,太大了內(nèi)存可能不夠,也不可行。

第二個(gè)辦法:NIO(非阻塞式IO)方式。因?yàn)閞ead()方法堵塞了,所有要開(kāi)辟多個(gè)線程,如果什么方法能使read()方法不堵塞,這樣就不用開(kāi)辟多個(gè)線程了,這就用到了另一個(gè)IO模型,NIO(非阻塞式IO)。

NIO

在NIO模式中,一切都是非阻塞的:accept()方法是非阻塞的,如果沒(méi)有客戶端連接,就返回?zé)o連接標(biāo)識(shí)。read()方法是非阻塞的,如果read()方法讀取不到數(shù)據(jù)就返回空閑中標(biāo)識(shí),如果讀取到數(shù)據(jù)時(shí)只阻塞read()方法讀數(shù)據(jù)的時(shí)間。在NIO模式中,只有一個(gè)線程:當(dāng)一個(gè)客戶端與服務(wù)端進(jìn)行連接,這個(gè)socket就會(huì)加入到一個(gè)數(shù)組中,隔一段時(shí)間遍歷一次,看這個(gè)socket的read()方法能否讀到數(shù)據(jù),這樣一個(gè)線程就能處理多個(gè)客戶端的連接和讀取了。

public class RedisServerNIO
{
    static ArrayList<SocketChannel> socketList = new ArrayList<>();
    static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException
    {
        System.out.println("---------RedisServerNIO 啟動(dòng)等待中......");
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress("127.0.0.1",6379));
        serverSocket.configureBlocking(false);//設(shè)置為非阻塞模式

        while (true) {
            for (SocketChannel element : socketList) {
                int read = element.read(byteBuffer);
                if(read > 0)
                {
                    System.out.println("-----讀取數(shù)據(jù): "+read);
                    byteBuffer.flip();
                    byte[] bytes = new byte[read];
                    byteBuffer.get(bytes);
                    System.out.println(new String(bytes));
                    byteBuffer.clear();
                }
            }
            SocketChannel socketChannel = serverSocket.accept();
            if(socketChannel != null) {
                System.out.println("-----成功連接: ");
                socketChannel.configureBlocking(false);//設(shè)置為非阻塞模式
                socketList.add(socketChannel);
                System.out.println("-----socketList size: "+socketList.size());
            }
        }
    }
}

啟動(dòng)RedisServerNIO,并啟動(dòng)RedisClient01,RedisClient02發(fā)送消息。

成功接收1號(hào)連接2號(hào)連接發(fā)送的消息,但是如何用單線程處理大量的連接呢?

IO多路復(fù)用模型

I/O多路復(fù)用在英文中其實(shí)叫 I/O multiplexing 。多個(gè)Socket復(fù)用一根網(wǎng)線這個(gè)功能是在內(nèi)核+驅(qū)動(dòng)層實(shí)現(xiàn)的。I/O multiplexing 這里面的 multiplexing 指的其實(shí)是在單個(gè)線程通過(guò)記錄跟蹤每一個(gè)Sock(I/O流)的狀態(tài)來(lái)同時(shí)管理多個(gè)I/O流. 目的是盡量多的提高服務(wù)器的吞吐能力。大家都用過(guò)nginx,nginx使用epoll接收請(qǐng)求,ngnix會(huì)有很多鏈接進(jìn)來(lái), epoll會(huì)把他們都監(jiān)視起來(lái),然后像撥開(kāi)關(guān)一樣,誰(shuí)有數(shù)據(jù)就撥向誰(shuí),然后調(diào)用相應(yīng)的代碼處理。redis類(lèi)似同理。

I/O:網(wǎng)絡(luò)I/O。

多路:多個(gè)客戶端連接(連接就是套接字描述符,即 socket 或者 channel) ,指的是多條 TCP 連接復(fù)用: 用一個(gè)進(jìn)程來(lái)處理多條的連接,使用單進(jìn)程就能夠?qū)崿F(xiàn)同時(shí)處理多個(gè)客戶端的連接實(shí)現(xiàn)了用一個(gè)進(jìn)程來(lái)處理大量的用戶連接。

IO multiplexing就是我們說(shuō)的select,poll,epoll,有些技術(shù)書(shū)籍也稱(chēng)這種IO方式為event driven IO事件驅(qū)動(dòng)IO。就是通過(guò)一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫(xiě)就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫(xiě)操作。可以基于一個(gè)阻塞對(duì)象并同時(shí)在多個(gè)描述符上等待就緒,而不是使用多個(gè)線程(每個(gè)文件描述符一個(gè)線程,每次new一個(gè)線程),這樣可以大大節(jié)省系統(tǒng)資源。所以,I/O 多路復(fù)用的特點(diǎn)是通過(guò)一種機(jī)制一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài),select,poll,epoll等函數(shù)就可以返回。

文件描述符

文件描述符(File descriptor)計(jì)算機(jī)科學(xué)中的一個(gè)術(shù)語(yǔ),是一個(gè)用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一個(gè)非負(fù)整數(shù)。實(shí)際上,它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開(kāi)文件的記錄表。當(dāng)程序打開(kāi)一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符。在程序設(shè)計(jì)中,一些涉及底層的程序編寫(xiě)往往會(huì)圍繞著文件描述符展開(kāi)。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統(tǒng)。

舉例描述IO多路復(fù)用模型

模擬一個(gè)tcp服務(wù)器處理30個(gè)客戶socket,一個(gè)監(jiān)考老師監(jiān)考多個(gè)學(xué)生,誰(shuí)舉手就應(yīng)答誰(shuí)。假設(shè)你是一個(gè)監(jiān)考老師,讓30個(gè)學(xué)生解答一道競(jìng)賽考題,然后負(fù)責(zé)驗(yàn)收學(xué)生答卷,你有下面幾個(gè)選擇:

第一種選擇:按順序逐個(gè)驗(yàn)收,先驗(yàn)收A,然后是B,之后是C、D。。。這中間如果有一個(gè)學(xué)生卡住,全班都會(huì)被耽誤,你用循環(huán)挨個(gè)處理socket,根本不具有并發(fā)能力。 

第二種選擇:你創(chuàng)建30個(gè)分身線程,每個(gè)分身線程檢查一個(gè)學(xué)生的答案是否正確。 這種類(lèi)似于為每一個(gè)用戶創(chuàng)建一個(gè)進(jìn)程或者線程處理連接。

第三種選擇,你站在講臺(tái)上等,誰(shuí)解答完誰(shuí)舉手。這時(shí)C、D舉手,表示他們解答問(wèn)題完畢,你下去依次檢查C、D的答案,然后繼續(xù)回到講臺(tái)上等。此時(shí)E、A又舉手,然后去處理E和A。這種就是IO復(fù)用模型。Linux下的select、poll和epoll就是干這個(gè)的。

將用戶socket對(duì)應(yīng)的fd注冊(cè)進(jìn)epoll,然后epoll幫你監(jiān)聽(tīng)哪些socket上有消息到達(dá),這樣就避免了大量的無(wú)用操作。此時(shí)的socket應(yīng)該采用非阻塞模式。這樣,整個(gè)過(guò)程只在調(diào)用select、poll、epoll這些調(diào)用的時(shí)候才會(huì)阻塞,收發(fā)客戶消息是不會(huì)阻塞的,整個(gè)進(jìn)程或者線程就被充分利用起來(lái),這就是事件驅(qū)動(dòng),所謂的reactor反應(yīng)模式。所謂 I/O 多路復(fù)用機(jī)制,就是說(shuō)通過(guò)一種考試監(jiān)考機(jī)制,一個(gè)老師可以監(jiān)視多個(gè)考生,一旦某個(gè)考生舉手想要交卷了,能夠通知監(jiān)考老師進(jìn)行相應(yīng)的收卷子或批改檢查操作。所以這種機(jī)制需要調(diào)用班主任(select/poll/epoll)來(lái)配合。多個(gè)考生被同一個(gè)班主任監(jiān)考,收完一個(gè)考試的卷子再處理其它人,無(wú)需等待所有考生,誰(shuí)先舉手就先響應(yīng)誰(shuí),當(dāng)又有考生舉手要交卷,監(jiān)考老師看到后從講臺(tái)走到考生位置,開(kāi)始進(jìn)行收卷處理。

Reactor設(shè)計(jì)模式

基于 I/O 復(fù)用模型:多個(gè)連接共用一個(gè)阻塞對(duì)象,應(yīng)用程序只需要在一個(gè)阻塞對(duì)象上等待,無(wú)需阻塞等待所有連接。當(dāng)某條連接有新的數(shù)據(jù)可以處理時(shí),操作系統(tǒng)通知應(yīng)用程序,線程從阻塞狀態(tài)返回,開(kāi)始進(jìn)行業(yè)務(wù)處理。Reactor 模式,是指通過(guò)一個(gè)或多個(gè)輸入同時(shí)傳遞給服務(wù)處理器的服務(wù)請(qǐng)求的事件驅(qū)動(dòng)處理模式。服務(wù)端程序處理傳入多路請(qǐng)求,并將它們同步分派給請(qǐng)求對(duì)應(yīng)的處理線程,Reactor 模式也叫 Dispatcher 模式。即 I/O 多了復(fù)用統(tǒng)一監(jiān)聽(tīng)事件,收到事件后分發(fā)(Dispatch 給某進(jìn)程),是編寫(xiě)高性能網(wǎng)絡(luò)服務(wù)器的必備技術(shù)。

Reactor 模式中有 2 個(gè)關(guān)鍵組成:

1)Reactor:Reactor 在一個(gè)單獨(dú)的線程中運(yùn)行,負(fù)責(zé)監(jiān)聽(tīng)和分發(fā)事件,分發(fā)給適當(dāng)?shù)奶幚沓绦騺?lái)對(duì) IO 事件做出反應(yīng)。 它就像公司的電話接線員,它接聽(tīng)來(lái)自客戶的電話并將線路轉(zhuǎn)移到適當(dāng)?shù)穆?lián)系人;

2)Handlers:處理程序執(zhí)行 I/O 事件要完成的實(shí)際事件,類(lèi)似于客戶想要與之交談的公司中的實(shí)際辦理人。Reactor 通過(guò)調(diào)度適當(dāng)?shù)奶幚沓绦騺?lái)響應(yīng) I/O 事件,處理程序執(zhí)行非阻塞操作。

那么現(xiàn)在我們?cè)谠賮?lái)看一下Redis單線程是如何處理那么多并發(fā)客戶端連接?為什么單線?為什么那么快?

Redis 是跑在單線程中的,所有的操作都是按照順序線性執(zhí)行的,但是由于讀寫(xiě)操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回,這會(huì)導(dǎo)致某一文件的 I/O 阻塞導(dǎo)致整個(gè)進(jìn)程無(wú)法對(duì)其它客戶提供服務(wù),而 I/O 多路復(fù)用就是為了解決這個(gè)問(wèn)題而出現(xiàn)。所謂 I/O 多路復(fù)用機(jī)制,就是說(shuō)通過(guò)一種機(jī)制,可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或?qū)懢途w),能夠通知程序進(jìn)行相應(yīng)的讀寫(xiě)操作。這種機(jī)制的使用需要 select 、 poll 、 epoll 來(lái)配合。多個(gè)連接共用一個(gè)阻塞對(duì)象,應(yīng)用程序只需要在一個(gè)阻塞對(duì)象上等待,無(wú)需阻塞等待所有連接。當(dāng)某條連接有新的數(shù)據(jù)可以處理時(shí),操作系統(tǒng)通知應(yīng)用程序,線程從阻塞狀態(tài)返回,開(kāi)始進(jìn)行業(yè)務(wù)處理

Redis 服務(wù)采用 Reactor 的方式來(lái)實(shí)現(xiàn)文件事件處理器(每一個(gè)網(wǎng)絡(luò)連接其實(shí)都對(duì)應(yīng)一個(gè)文件描述符) 

Redis基于Reactor模式開(kāi)發(fā)了網(wǎng)絡(luò)事件處理器,這個(gè)處理器被稱(chēng)為文件事件處理器。它的組成結(jié)構(gòu)為4部分:多個(gè)套接字、IO多路復(fù)用程序、文件事件分派器、事件處理器。因?yàn)槲募录峙善麝?duì)列的消費(fèi)是單線程的,所以Redis才叫單線程模型。

IO多路復(fù)用的具體實(shí)現(xiàn)select、poll、epoll

select

select 其實(shí)就是把NIO中用戶態(tài)要遍歷的fd數(shù)組(我們的每一個(gè)socket鏈接,安裝進(jìn)ArrayList里面的那個(gè))拷貝到了內(nèi)核態(tài),讓內(nèi)核態(tài)來(lái)遍歷,因?yàn)橛脩魬B(tài)判斷socket是否有數(shù)據(jù)還是要調(diào)用內(nèi)核態(tài)的,所有拷貝到內(nèi)核態(tài)后,這樣遍歷判斷的時(shí)候就不用一直用戶態(tài)和內(nèi)核態(tài)頻繁切換了。select方式,既做到了一個(gè)線程處理多個(gè)客戶端連接(文件描述符),又減少了系統(tǒng)調(diào)用的開(kāi)銷(xiāo)(多個(gè)文件描述符只有一次 select 的系統(tǒng)調(diào)用 + N次就緒狀態(tài)的文件描述符的 read 系統(tǒng)調(diào)用。

select函數(shù)的執(zhí)行流程:

1、select是一個(gè)阻塞函數(shù),當(dāng)沒(méi)有數(shù)據(jù)時(shí),會(huì)一直阻塞在select那一行。

2、當(dāng)有數(shù)據(jù)時(shí)會(huì)將rset中對(duì)應(yīng)的那一位置為1。

3、select函數(shù)返回,不再阻塞。

4、遍歷文件描述符數(shù)值,判斷那個(gè)fd被置位了。

5、讀取數(shù)據(jù),然后處理。

優(yōu)點(diǎn):

select系統(tǒng)調(diào)用后,返回了一個(gè)置位后的&rset,這樣用戶態(tài)只需進(jìn)行很簡(jiǎn)單的二進(jìn)制比較,就能很快知道哪些socket需要read數(shù)據(jù),有效提高了效率。

缺點(diǎn):

1、bitmap最大1024位,一個(gè)進(jìn)程最多只能處理1024個(gè)客戶端。

2、&rset不可重用,每次socket有數(shù)據(jù)就相應(yīng)的位會(huì)被置位。

3、文件描述符數(shù)組拷貝到了內(nèi)核態(tài)(只不過(guò)無(wú)系統(tǒng)調(diào)用切換上下文的開(kāi)銷(xiāo)。(內(nèi)核層可優(yōu)化為異步事件通知)),仍然有開(kāi)銷(xiāo)。select 調(diào)用需要傳入 fd 數(shù)組,需要拷貝一份到內(nèi)核,高并發(fā)場(chǎng)景下這樣的拷貝消耗的資源是驚人的。(可優(yōu)化為不復(fù)制)

4、select并沒(méi)有通知用戶態(tài)哪一個(gè)socket有數(shù)據(jù),仍然需要O(n)的遍歷。select 僅僅返回可讀文件描述符的個(gè)數(shù),具體哪個(gè)可讀還是要用戶自己遍歷。(可優(yōu)化為只返回給用戶就緒的文件描述符,無(wú)需用戶做無(wú)效的遍歷)。

poll

poll的執(zhí)行流程:

1、將五個(gè)fd從用戶態(tài)拷貝到內(nèi)核態(tài)。

2、poll為阻塞方法,執(zhí)行poll方法,如果有數(shù)據(jù)會(huì)將fd對(duì)應(yīng)的revents置為POLLIN。

3、poll方法返回。

4、循環(huán)遍歷,查找哪個(gè)fd被置位為POLLIN了。

5、將revents重置為0便于復(fù)用。

6、對(duì)置位的fd進(jìn)行讀取和處理。

優(yōu)點(diǎn):

1、poll使用pollfd數(shù)組來(lái)代替select中的bitmap,數(shù)組沒(méi)有1024的限制,可以一次管理更多的client。它和 select 的主要區(qū)別就是,去掉了 select 只能監(jiān)聽(tīng) 1024 個(gè)文件描述符的限制。

2、當(dāng)pollfds數(shù)組中有事件發(fā)生,相應(yīng)的revents置位為1,遍歷的時(shí)候又置位回零,實(shí)現(xiàn)了pollfd數(shù)組的重用。

缺點(diǎn):

poll 解決了select缺點(diǎn)中的前兩條,其本質(zhì)原理還是select的方法,還存在select中原來(lái)的問(wèn)題。

1、pollfds數(shù)組拷貝到了內(nèi)核態(tài),仍然有開(kāi)銷(xiāo)。

2、poll并沒(méi)有通知用戶態(tài)哪一個(gè)socket有數(shù)據(jù),仍然需要O(n)的遍歷。

epoll

epoll 是非阻賽的。

epoll的執(zhí)行流程:

1、當(dāng)有數(shù)據(jù)的時(shí)候,會(huì)把相應(yīng)的文件描述符“置位”,但是epool沒(méi)有revent標(biāo)志位,所以并不是真正的置位。這時(shí)候會(huì)把有數(shù)據(jù)的文件描述符放到隊(duì)首。

2、epoll會(huì)返回有數(shù)據(jù)的文件描述符的個(gè)數(shù)。

3、根據(jù)返回的個(gè)數(shù) 讀取前N個(gè)文件描述符即可。

4、讀取、處理。

三步調(diào)用:

1、epoll_create:創(chuàng)建一個(gè)epoll句柄。

2、epoll_ctl:向內(nèi)核添加、修改或刪除要監(jiān)控的文件描述符。

3、epoll_wait:類(lèi)似發(fā)起了select()調(diào)用。

總結(jié)

多路復(fù)用快的原因在于,操作系統(tǒng)提供了這樣的系統(tǒng)調(diào)用,使得原來(lái)的 while 循環(huán)里多次系統(tǒng)調(diào)用,變成了一次系統(tǒng)調(diào)用 + 內(nèi)核層遍歷這些文件描述符。

epoll是現(xiàn)在最先進(jìn)的IO多路復(fù)用器,Redis、Nginx,linux中的Java NIO都使用的是epoll。這里“多路”指的是多個(gè)網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個(gè)線程。

1、一個(gè)socket的生命周期中只有一次從用戶態(tài)拷貝到內(nèi)核態(tài)的過(guò)程,開(kāi)銷(xiāo)小。

2、使用event事件通知機(jī)制,每次socket中有數(shù)據(jù)會(huì)主動(dòng)通知內(nèi)核,并加入到就緒鏈表中,不需要遍歷所有的socket。

在多路復(fù)用IO模型中,會(huì)有一個(gè)內(nèi)核線程不斷地去輪詢多個(gè) socket 的狀態(tài),只有當(dāng)真正讀寫(xiě)事件發(fā)送時(shí),才真正調(diào)用實(shí)際的IO讀寫(xiě)操作。因?yàn)樵诙嗦窂?fù)用IO模型中,只需要使用一個(gè)線程就可以管理多個(gè)socket,系統(tǒng)不需要建立新的進(jìn)程或者線程,也不必維護(hù)這些線程和進(jìn)程,并且只有真正有讀寫(xiě)事件進(jìn)行時(shí),才會(huì)使用IO資源,所以它大大減少來(lái)資源占用。

多路I/O復(fù)用模型是利用 select、poll、epoll 可以同時(shí)監(jiān)察多個(gè)流的 I/O 事件的能力,在空閑的時(shí)候,會(huì)把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)流有 I/O 事件時(shí),就從阻塞態(tài)中喚醒,于是程序就會(huì)輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無(wú)用操作。

 采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò) IO 的時(shí)間消耗),且 Redis 在內(nèi)存中操作數(shù)據(jù)的速度非???,也就是說(shuō)內(nèi)存內(nèi)的操作不會(huì)成為影響Redis性能的瓶頸。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Redis 在真實(shí)世界的 5 個(gè)用法

    Redis 在真實(shí)世界的 5 個(gè)用法

    Redis是一個(gè)開(kāi)源的使用ANSI C語(yǔ)言編寫(xiě)、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫(kù),并提供多種語(yǔ)言的API這篇文章主要介紹了Redis 在真實(shí)世界的 5 個(gè)用法,需要的朋友可以參考下
    2018-03-03
  • Redis模糊查詢的幾種實(shí)現(xiàn)方法

    Redis模糊查詢的幾種實(shí)現(xiàn)方法

    本文主要介紹了Redis模糊查詢的幾種實(shí)現(xiàn)方法,包括兩種方法KEYS , SCAN,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02
  • redis中RedissonLock如何實(shí)現(xiàn)等待鎖的

    redis中RedissonLock如何實(shí)現(xiàn)等待鎖的

    本文主要介紹了redis中RedissonLock如何實(shí)現(xiàn)等待鎖的,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Redis配置文件最佳實(shí)踐

    Redis配置文件最佳實(shí)踐

    這篇文章主要介紹了Redis配置文件詳解,本文主要是根據(jù)Redis6.0.x版本的配置文件講解,其它版本的也可以當(dāng)做一個(gè)參考,需要的朋友可以參考下
    2025-05-05
  • Redis搭建一主多從的實(shí)現(xiàn)步驟

    Redis搭建一主多從的實(shí)現(xiàn)步驟

    本文主要介紹了在Linux環(huán)境下配置Redis的一主兩從架構(gòu),包括創(chuàng)建配置文件,設(shè)置不同端口,啟動(dòng)服務(wù),以及通過(guò)inforeplication命令檢查復(fù)制狀態(tài),感興趣都可以了解一下
    2025-10-10
  • Redis?ziplist?壓縮列表的源碼解析

    Redis?ziplist?壓縮列表的源碼解析

    ziplist 是一個(gè)經(jīng)過(guò)特殊編碼的雙向鏈表,旨在提高內(nèi)存效率,它存儲(chǔ)字符串和整數(shù)值,其中整數(shù)被編碼為實(shí)際整數(shù)而不是一系列字符,這篇文章主要介紹了Redis?ziplist?壓縮列表的源碼解析,需要的朋友可以參考下
    2022-06-06
  • 使用Redis實(shí)現(xiàn)分布式鎖的代碼演示

    使用Redis實(shí)現(xiàn)分布式鎖的代碼演示

    edis作為一個(gè)高性能的內(nèi)存數(shù)據(jù)庫(kù),提供了多種機(jī)制來(lái)實(shí)現(xiàn)分布式鎖,本文將詳細(xì)介紹如何使用Redis實(shí)現(xiàn)分布式鎖,感興趣的朋友一起看看吧
    2025-04-04
  • Redis RDB快照持久化及寫(xiě)操作禁止問(wèn)題排查與解決

    Redis RDB快照持久化及寫(xiě)操作禁止問(wèn)題排查與解決

    本文主要介紹了Redis RDB快照持久化及寫(xiě)操作禁止問(wèn)題排查與解決,由于?stop-writes-on-bgsave-error?選項(xiàng)處于啟用狀態(tài),所以寫(xiě)操作被禁止,下面就來(lái)介紹一下,感興趣的可以了解一下
    2025-04-04
  • Redis定時(shí)任務(wù)原理的實(shí)現(xiàn)

    Redis定時(shí)任務(wù)原理的實(shí)現(xiàn)

    本文主要是基于?redis?6.2?源碼進(jìn)行分析定時(shí)事件的數(shù)據(jù)結(jié)構(gòu)和常見(jiàn)操作,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 談?wù)凴edis分布式鎖的正確實(shí)現(xiàn)方法

    談?wù)凴edis分布式鎖的正確實(shí)現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于Redis分布式鎖的正確實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08

最新評(píng)論

绥化市| 清徐县| 库伦旗| 平邑县| 靖远县| 贵溪市| 文山县| 新河县| 乐至县| 伽师县| 铁岭市| 那坡县| 凤山市| 凤山县| 静宁县| 庄河市| 林西县| 抚顺县| 藁城市| 永和县| 渭南市| 土默特右旗| 三明市| 望都县| 兴仁县| 唐山市| 介休市| 麻栗坡县| 太仓市| 广饶县| 新绛县| 丽水市| 景宁| 湖北省| 瑞丽市| 铜川市| 伊春市| 三亚市| 本溪市| 福安市| 涟源市|