精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Redis 是單線程模型?

數(shù)據(jù)庫 Redis
Redis6.0引入多線程IO,但多線程部分只是用來處理網(wǎng)絡(luò)數(shù)據(jù)的讀寫和協(xié)議解析,執(zhí)行命令仍然是單線程。通過開啟多線程IO,并設(shè)置合適的CPU數(shù)量,可以提升訪問請(qǐng)求一倍以上。

一、背景

二、Redis6.0多線程IO概述

    1.  參數(shù)與配置

    2. 執(zhí)行流程概述

三、源碼分析

    1.  初始化

    2. 讀數(shù)據(jù)流程

    3. 寫數(shù)據(jù)流程

    4. 多線程IO動(dòng)態(tài)暫停與開啟

四、性能對(duì)比

    1.  測(cè)試環(huán)境

    2. Redis版本

    3. 壓測(cè)命令

    4. 統(tǒng)計(jì)結(jié)果

    5. 結(jié)論

五、6.0多線程IO不足

六、總結(jié)

一、背景

使用過Redis的同學(xué)肯定都了解過一個(gè)說法,說Redis是單線程模型,那么實(shí)際情況是怎樣的呢?

其實(shí),我們常說Redis是單線程模型,是指Redis采用單線程的事件驅(qū)動(dòng)模型,只有并且只會(huì)在一個(gè)主線程中執(zhí)行Redis命令操作,這意味著它在處理請(qǐng)求時(shí)不使用復(fù)雜的上下文切換或鎖機(jī)制。盡管只是單線程的架構(gòu),但Redis通過非阻塞的I/O操作和高效的事件循環(huán)來處理大量的并發(fā)連接,性能仍然非常高。

然而在Redis4.0開始也引入了一些后臺(tái)線程執(zhí)行異步淘汰、異步刪除過期key、異步執(zhí)行大key刪除等任務(wù),然后,在Redis6.0中引入了多線程IO特性,將Redis單節(jié)點(diǎn)訪問請(qǐng)求從10W提升到20W。

而在去年Valkey社區(qū)發(fā)布的Valkey8.0版本,在I/O線程系統(tǒng)上進(jìn)行了重大升級(jí),特別是異步I/O線程的引入,使主線程和I/O線程能夠并行工作,可實(shí)現(xiàn)最大化服務(wù)吞吐量并減少瓶頸,使得Valkey單節(jié)點(diǎn)訪問請(qǐng)求可以提升到100W。

那么在Redis6.0和Valkey8.0中多線程IO是怎么回事呢?是否改變了Redis原有單線程模型?

  • 2024年,Redis商業(yè)支持公司Redis Labs宣布Redis核心代碼的許可證從BSD變更為RSALv2,明確禁止云廠商提供Redis托管服務(wù),這一決定直接導(dǎo)致社區(qū)分裂。
  • 為維護(hù)開源自由,Linux基金會(huì)聯(lián)合多家科技公司(包括AWS、Google、Cloud、Oracle等)宣布支持Valkey,作為Redis的替代分支。
  • Valkey8.0系Valkey社區(qū)發(fā)布的首個(gè)主要大版本。
  • 最新消息,在Redis項(xiàng)目創(chuàng)始人antirez今年加入Redis商業(yè)公司5個(gè)月后,Redis宣傳從Redis8開始,Redis項(xiàng)目重新開源。

本篇文章主要介紹Redis6.0多線程IO特性。

二、Redis6.0 多線程 IO 概述

Redis6.0引入多線程IO,但多線程部分只是用來處理網(wǎng)絡(luò)數(shù)據(jù)的讀寫和協(xié)議解析,執(zhí)行命令仍然是單線程。默認(rèn)是不開啟的,需要進(jìn)程啟動(dòng)前開啟配置,并且在運(yùn)行期間無法通過 config set 命令動(dòng)態(tài)修改。

參數(shù)與配置

多線程IO涉及下面兩個(gè)配置參數(shù):

# io-threads 4  IO 線程數(shù)量
# io-threads-do-reads no  讀數(shù)據(jù)及數(shù)據(jù)解析是否也用 IO 線程
  •  io-threads 表示IO線程數(shù)量, io-threads 設(shè)置為1時(shí)(代碼中默認(rèn)值),表示只使用主線程,不開啟多線程IO。因此,若要配置開啟多線程IO,需要設(shè)置 io-threads 大于1,但不可以超過最大值128。
  • 但在默認(rèn)情況下,Redis只將多線程IO用于向客戶端寫數(shù)據(jù),因?yàn)樽髡哒J(rèn)為通常使用多線程執(zhí)行讀數(shù)據(jù)的操作幫助不是很大。如果需要使用多線程用于讀數(shù)據(jù)和解析數(shù)據(jù),則需要將參數(shù) io-threads-do-reads 設(shè)置為 yes 。
  • 此兩項(xiàng)配置參數(shù)在Redis運(yùn)行期間無法通過 config set 命令修改,并且開啟SSL時(shí),不支持多線程IO特性。
  • 若機(jī)器CPU將至少超過4核時(shí),則建議開啟,并且至少保留一個(gè)備用CPU核,使用超過8個(gè)線程可能并不會(huì)有多少幫助。

執(zhí)行流程概述

Redis6.0引入多線程IO后,讀寫數(shù)據(jù)執(zhí)行流程如下所示:

圖片圖片

流程簡(jiǎn)述

  1. 主線程負(fù)責(zé)接收建立連接請(qǐng)求,獲取socket放入全局等待讀處理隊(duì)列。
  2. 主線程處理完讀事件之后,通過RR(Round Robin)將這些連接分配給這些IO線程,也會(huì)分配給主線程自己。
  3. 主線程先讀取分配給自己的客戶端數(shù)據(jù),然后阻塞等待其他IO線程讀取socket完畢。
  4. IO線程將請(qǐng)求數(shù)據(jù)讀取并解析完成(這里只是讀數(shù)據(jù)和解析、并不執(zhí)行)。
  5. 主線程通過單線程的方式執(zhí)行請(qǐng)求命令。
  6. 主線程通過RR(Round Robin)將回寫客戶端事件分配給這些IO線程,也會(huì)分配給主線程自己。
  7. 主線程同樣執(zhí)行部分寫數(shù)據(jù)到客戶端,然后阻塞等待IO線程將數(shù)據(jù)回寫socket完畢。

設(shè)計(jì)特點(diǎn)

  1. IO線程要么同時(shí)在讀socket,要么同時(shí)在寫,不會(huì)同時(shí)讀和寫。
  2. IO線程只負(fù)責(zé)讀寫socket解析命令,不負(fù)責(zé)命令執(zhí)行。
  3. 主線程也會(huì)參與數(shù)據(jù)的讀寫。

三、源碼分析

多線程IO相關(guān)源代碼都在源文件networking.c中最下面。

初始化

主線程在main函數(shù)中調(diào)用InitServerLast函數(shù),InitServerLast函數(shù)中調(diào)用initThreadedIO函數(shù),在initThreadedIO函數(shù)中根據(jù)配置文件中的線程數(shù)量,創(chuàng)建對(duì)應(yīng)數(shù)量的IO工作線程數(shù)量。

/* Initialize the data structures needed for threaded I/O. */
void initThreadedIO(void) {
    io_threads_active = 0; /* We start with threads not active. */
    
    /* Don't spawn any thread if the user selected a single thread:
     * we'll handle I/O directly from the main thread. */
    if (server.io_threads_num == 1) return;
    
    if (server.io_threads_num > IO_THREADS_MAX_NUM) {
        serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
                             "The maximum number is %d.", IO_THREADS_MAX_NUM);
        exit(1);
    }
   
    /* Spawn and initialize the I/O threads. */
    for (int i = 0; i < server.io_threads_num; i++) {
        /* Things we do for all the threads including the main thread. */
        io_threads_list[i] = listCreate();
        if (i == 0) continue; /* Thread 0 is the main thread. */
       
        /* Things we do only for the additional threads. */
        pthread_t tid;
        pthread_mutex_init(&io_threads_mutex[i],NULL);
        io_threads_pending[i] = 0;
        pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
        if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
            serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
            exit(1);
        }
        io_threads[i] = tid;
    }
}
  • 如果 io_threads_num 的數(shù)量為1,則只運(yùn)行主線程, io_threads_num 的IO線程數(shù)量不允許超過 128。
  • 序號(hào)為0的線程是主線程,因此實(shí)際的工作線程數(shù)目是io-threads - 1。

初始化流程

  • 為包括主線程在內(nèi)的每個(gè)線程分配list列表,用于后續(xù)保存待處理的客戶端。
  • 為主線程以外的其他IO線程初始化互斥對(duì)象mutex,但是立即調(diào)用pthread_mutex_lock占有互斥量,將io_threads_pending[i]設(shè)置為0,接著創(chuàng)建對(duì)應(yīng)的IO工作線程。
  • 占用互斥量是為了創(chuàng)建IO工作線程后,可暫時(shí)等待后續(xù)啟動(dòng)IO線程的工作,因?yàn)镮OThreadMain函數(shù)在io_threads_pending[id] == 0時(shí)也調(diào)用了獲取mutex,所以此時(shí)無法繼續(xù)向下運(yùn)行,等待啟動(dòng)。
  • 在startThreadedIO函數(shù)中會(huì)釋放mutex來啟動(dòng)IO線程工作。何時(shí)調(diào)用startThreadedIO打開多線程IO,具體見下文的「多線程IO動(dòng)態(tài)暫停與開啟」。

IO 線程主函數(shù)

IO線程主函數(shù)代碼如下所示:

void *IOThreadMain(void *myid) {
    /* The ID is the thread number (from 0 to server.iothreads_num-1), and is
     * used by the thread to just manipulate a single sub-array of clients. */
    long id = (unsigned long)myid;
    char thdname[16];
   
    snprintf(thdname, sizeof(thdname), "io_thd_%ld", id);
    redis_set_thread_title(thdname);
    redisSetCpuAffinity(server.server_cpulist);
   
    while(1) {
        /* Wait for start */
        for (int j = 0; j < 1000000; j++) {
            if (io_threads_pending[id] != 0) break;
        }
       
        /* Give the main thread a chance to stop this thread. */
        if (io_threads_pending[id] == 0) {
            pthread_mutex_lock(&io_threads_mutex[id]);
            pthread_mutex_unlock(&io_threads_mutex[id]);
            continue;
        }
       
        serverAssert(io_threads_pending[id] != 0);
        
        if (tio_debug) printf("[%ld] %d to handle\n", id, (int)listLength(io_threads_list[id]));
        
        /* Process: note that the main thread will never touch our list
         * before we drop the pending count to 0. */
        listIter li;
        listNode *ln;
        listRewind(io_threads_list[id],&li);
        while((ln = listNext(&li))) {
            client *c = listNodeValue(ln);
            if (io_threads_op == IO_THREADS_OP_WRITE) {
                writeToClient(c,0);
            } else if (io_threads_op == IO_THREADS_OP_READ) {
                readQueryFromClient(c->conn);
            } else {
                serverPanic("io_threads_op value is unknown");
            }
        }
        listEmpty(io_threads_list[id]);
        io_threads_pending[id] = 0;
       
        if (tio_debug) printf("[%ld] Done\n", id);
    }
}

從IO線程主函數(shù)邏輯可以看到:

  • 如果IO線程等待處理任務(wù)數(shù)量為0,則IO線程一直在空循環(huán),因此后面主線程給IO線程分發(fā)任務(wù)后,需要設(shè)置IO線程待處理任務(wù)數(shù) io_threads_pending[id] ,才會(huì)觸發(fā)IO線程工作。
  • 如果IO線程等待處理任務(wù)數(shù)量為0,并且未獲取到mutex鎖,則會(huì)等待獲取鎖,暫停運(yùn)行,由于主線程在創(chuàng)建IO線程之前先獲取了鎖,因此IO線程剛啟動(dòng)時(shí)是暫停運(yùn)行狀態(tài),需要等待主線程釋放鎖,啟動(dòng)IO線程。
  • IO線程待處理任務(wù)數(shù)為0時(shí),獲取到鎖并再次釋放鎖,是為了讓主線程可以暫停IO線程。
  • 只有io_threads_pending[id]不為0時(shí),則繼續(xù)向下執(zhí)行操作,根據(jù)io_threads_op決定是讀客戶端還是寫客戶端,從這里也可以看出IO線程要么同時(shí)讀,要么同時(shí)寫。

讀數(shù)據(jù)流程

主線程將待讀數(shù)據(jù)客戶端加入隊(duì)列

當(dāng)客戶端連接有讀事件時(shí),會(huì)觸發(fā)調(diào)用readQueryFromClient函數(shù),在該函數(shù)中會(huì)調(diào)用postponeClientRead。

void readQueryFromClient(connection *conn) {
    client *c = connGetPrivateData(conn);
    int nread, readlen;
    size_t qblen;
    
    /* Check if we want to read from the client later when exiting from
     * the event loop. This is the case if threaded I/O is enabled. */
    if (postponeClientRead(c)) return;
    ......以下省略
}


/* Return 1 if we want to handle the client read later using threaded I/O.
 * This is called by the readable handler of the event loop.
 * As a side effect of calling this function the client is put in the
 * pending read clients and flagged as such. */
int postponeClientRead(client *c) {
    if (io_threads_active &&
        server.io_threads_do_reads &&
        !ProcessingEventsWhileBlocked &&
        !(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
    {
        c->flags |= CLIENT_PENDING_READ;
        listAddNodeHead(server.clients_pending_read,c);
        return 1;
    } else {
        return 0;
    }
}

如果開啟多線程,并且開啟多線程讀(io_threads_do_reads 為 yes),則將客戶端標(biāo)記為CLIENT_PENDING_READ,并且加入clients_pending_read列表。

然后readQueryFromClient函數(shù)中就立即返回,主線程沒有執(zhí)行從客戶端連接中讀取的數(shù)據(jù)相關(guān)邏輯,讀取了客戶端數(shù)據(jù)行為等待后續(xù)各個(gè)IO線程執(zhí)行。

主線程分發(fā)并阻塞等待

主線程在beforeSleep函數(shù)中會(huì)調(diào)用handleClientsWithPendingReadsUsingThreads函數(shù)。

/* When threaded I/O is also enabled for the reading + parsing side, the
 * readable handler will just put normal clients into a queue of clients to
 * process (instead of serving them synchronously). This function runs
 * the queue using the I/O threads, and process them in order to accumulate
 * the reads in the buffers, and also parse the first command available
 * rendering it in the client structures. */
int handleClientsWithPendingReadsUsingThreads(void) {
    if (!io_threads_active || !server.io_threads_do_reads) return 0;
    int processed = listLength(server.clients_pending_read);
    if (processed == 0) return 0;
   
    if (tio_debug) printf("%d TOTAL READ pending clients\n", processed);
   
    /* Distribute the clients across N different lists. */
    listIter li;
    listNode *ln;
    listRewind(server.clients_pending_read,&li);
    int item_id = 0;
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        int target_id = item_id % server.io_threads_num;
        listAddNodeTail(io_threads_list[target_id],c);
        item_id++;
    }
    
    /* Give the start condition to the waiting threads, by setting the
     * start condition atomic var. */
    io_threads_op = IO_THREADS_OP_READ;
    for (int j = 1; j < server.io_threads_num; j++) {
        int count = listLength(io_threads_list[j]);
        io_threads_pending[j] = count;
    }
   
    /* Also use the main thread to process a slice of clients. */
    listRewind(io_threads_list[0],&li);
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        readQueryFromClient(c->conn);
    }
    listEmpty(io_threads_list[0]);
    
    /* Wait for all the other threads to end their work. */
    while(1) {
        unsigned long pending = 0;
        for (int j = 1; j < server.io_threads_num; j++)
            pending += io_threads_pending[j];
        if (pending == 0) break;
    }
    if (tio_debug) printf("I/O READ All threads finshed\n");
   
    /* Run the list of clients again to process the new buffers. */
    while(listLength(server.clients_pending_read)) {
        ln = listFirst(server.clients_pending_read);
        client *c = listNodeValue(ln);
        c->flags &= ~CLIENT_PENDING_READ;
        listDelNode(server.clients_pending_read,ln);
        
        if (c->flags & CLIENT_PENDING_COMMAND) {
            c->flags &= ~CLIENT_PENDING_COMMAND;
            if (processCommandAndResetClient(c) == C_ERR) {
                /* If the client is no longer valid, we avoid
                 * processing the client later. So we just go
                 * to the next. */
                continue;
            }
        }
        processInputBuffer(c);
    }
    return processed;
}
  • 先檢查是否開啟多線程,以及是否開啟多線程讀數(shù)據(jù)(io_threads_do_reads),未開啟直接返回。
  • 檢查隊(duì)列clients_pending_read長(zhǎng)度,為0直接返回,說明沒有待讀事件。
  • 遍歷clients_pending_read隊(duì)列,通過RR算法,將隊(duì)列中的客戶端循環(huán)分配給各個(gè)IO線程,包括主線程本身。
  • 設(shè)置io_threads_op = IO_THREADS_OP_READ,并且將io_threads_pending數(shù)組中各個(gè)位置值設(shè)置為對(duì)應(yīng)各個(gè)IO線程分配到的客戶端數(shù)量,如上面介紹,目的是為了使IO線程工作。
  • 主線程開始讀取客戶端數(shù)據(jù),因?yàn)橹骶€程也分配了任務(wù)。
  • 主線程阻塞等待,直到所有的IO線程都完成讀數(shù)據(jù)工作。
  • 主線程執(zhí)行命令。

IO 線程讀數(shù)據(jù)

在IO線程主函數(shù)中,如果 io_threads_op == IO_THREADS_OP_READ ,則調(diào)用readQueryFromClient從網(wǎng)絡(luò)中讀取數(shù)據(jù)。

IO 線程讀取數(shù)據(jù)后,不會(huì)執(zhí)行命令。

在readQueryFromClient函數(shù)中,最后會(huì)執(zhí)行processInputBuffer函數(shù),在processInputBuffe函數(shù)中,如IO線程檢查到客戶端設(shè)置了CLIENT_PENDING_READ標(biāo)志,則不執(zhí)行命令,直接返回。

......省略
/* If we are in the context of an I/O thread, we can't really
             * execute the command here. All we can do is to flag the client
             * as one that needs to process the command. */
            if (c->flags & CLIENT_PENDING_READ) {
                c->flags |= CLIENT_PENDING_COMMAND;
                break;
            }
            ...... 省略

寫數(shù)據(jù)流程

命令處理完成后,依次調(diào)用:

addReply-->prepareClientToWrite-->clientInstallWriteHandler,將待寫客戶端加入隊(duì)列clients_pending_write。

void clientInstallWriteHandler(client *c) {
    /* Schedule the client to write the output buffers to the socket only
     * if not already done and, for slaves, if the slave can actually receive
     * writes at this stage. */
    if (!(c->flags & CLIENT_PENDING_WRITE) &&
        (c->replstate == REPL_STATE_NONE ||
         (c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
    {
        /* Here instead of installing the write handler, we just flag the
         * client and put it into a list of clients that have something
         * to write to the socket. This way before re-entering the event
         * loop, we can try to directly write to the client sockets avoiding
         * a system call. We'll only really install the write handler if
         * we'll not be able to write the whole reply at once. */
        c->flags |= CLIENT_PENDING_WRITE;
        listAddNodeHead(server.clients_pending_write,c);
    }
}

在beforeSleep函數(shù)中調(diào)用handleClientsWithPendingWritesUsingThreads。

int handleClientsWithPendingWritesUsingThreads(void) {
    int processed = listLength(server.clients_pending_write);
    if (processed == 0) return 0; /* Return ASAP if there are no clients. */
    
    /* If I/O threads are disabled or we have few clients to serve, don't
     * use I/O threads, but thejboring synchronous code. */
    if (server.io_threads_num == 1 || stopThreadedIOIfNeeded()) {
        return handleClientsWithPendingWrites();
    }
    
    /* Start threads if needed. */
    if (!io_threads_active) startThreadedIO();
   
    if (tio_debug) printf("%d TOTAL WRITE pending clients\n", processed);
    
    /* Distribute the clients across N different lists. */
    listIter li;
    listNode *ln;
    listRewind(server.clients_pending_write,&li);
    int item_id = 0;
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        c->flags &= ~CLIENT_PENDING_WRITE;
        int target_id = item_id % server.io_threads_num;
        listAddNodeTail(io_threads_list[target_id],c);
        item_id++;
    }
   
    /* Give the start condition to the waiting threads, by setting the
     * start condition atomic var. */
    io_threads_op = IO_THREADS_OP_WRITE;
    for (int j = 1; j < server.io_threads_num; j++) {
        int count = listLength(io_threads_list[j]);
        io_threads_pending[j] = count;
    }
    
    /* Also use the main thread to process a slice of clients. */
    listRewind(io_threads_list[0],&li);
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        writeToClient(c,0);
    }
    listEmpty(io_threads_list[0]);
   
    /* Wait for all the other threads to end their work. */
    while(1) {
        unsigned long pending = 0;
        for (int j = 1; j < server.io_threads_num; j++)
            pending += io_threads_pending[j];
        if (pending == 0) break;
    }
    if (tio_debug) printf("I/O WRITE All threads finshed\n");
    
    /* Run the list of clients again to install the write handler where
     * needed. */
    listRewind(server.clients_pending_write,&li);
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
       
        /* Install the write handler if there are pending writes in some
         * of the clients. */
        if (clientHasPendingReplies(c) &&
                connSetWriteHandler(c->conn, sendReplyToClient) == AE_ERR)
        {
            freeClientAsync(c);
        }
    }
    listEmpty(server.clients_pending_write);
    return processed;
}
  1. 判斷clients_pending_write隊(duì)列的長(zhǎng)度,如果為0則直接返回。
  2. 判斷是否開啟了多線程,若只有很少的客戶端需要寫,則不使用多線程IO,直接在主線程完成寫操作。
  3. 如果使用多線程IO來完成寫數(shù)據(jù),則需要判斷是否先開啟多線程IO(因?yàn)闀?huì)動(dòng)態(tài)開啟與暫停)。
  4. 遍歷clients_pending_write隊(duì)列,通過RR算法,循環(huán)將所有客戶端分配給各個(gè)IO線程,包括主線程自身。
  5. 設(shè)置io_threads_op = IO_THREADS_OP_WRITE,并且將io_threads_pending數(shù)組中各個(gè)位置值設(shè)置為對(duì)應(yīng)的各個(gè)IO線程分配到的客戶端數(shù)量,目的是為了使IO線程工作。
  6. 主線程開始寫客戶端數(shù)據(jù),因?yàn)橹骶€程也分配了任務(wù),寫完清空任務(wù)隊(duì)列。
  7. 阻塞等待,直到所有IO線程完成寫數(shù)據(jù)工作。
  8. 再次遍歷所有客戶端,如果有需要,為客戶端在事件循環(huán)上安裝寫句柄函數(shù),等待事件回調(diào)。

多線程 IO 動(dòng)態(tài)暫停與開啟

從上面的寫數(shù)據(jù)的流程中可以看到,在Redis運(yùn)行過程中多線程IO是會(huì)動(dòng)態(tài)暫停與開啟的。

在上面的寫數(shù)據(jù)流程中,先調(diào)用stopThreadedIOIfNeeded函數(shù)判斷是否需要暫停多線程IO,當(dāng)?shù)却龑懙目蛻舳藬?shù)量低于線程數(shù)的2倍時(shí),會(huì)暫停多線程IO,否則就會(huì)打開多線程。

int stopThreadedIOIfNeeded(void) {
    int pending = listLength(server.clients_pending_write);
    
    /* Return ASAP if IO threads are disabled (single threaded mode). */
    if (server.io_threads_num == 1) return 1;
   
    if (pending < (server.io_threads_num*2)) {
        if (io_threads_active) stopThreadedIO();
        return 1;
    } else {
        return 0;
    }
}

在寫數(shù)據(jù)流程handleClientsWithPendingWritesUsingThreads函數(shù)中,stopThreadedIOIfNeeded返回0的話,就會(huì)執(zhí)行下面的startThreadedIO函數(shù),開啟多線程IO。

void startThreadedIO(void) {
    serverAssert(server.io_threads_active == 0);
    for (int j = 1; j < server.io_threads_num; j++)
        pthread_mutex_unlock(&io_threads_mutex[j]);
    server.io_threads_active = 1;
}


void stopThreadedIO(void) {
    /* We may have still clients with pending reads when this function
     * is called: handle them before stopping the threads. */
    handleClientsWithPendingReadsUsingThreads();
    serverAssert(server.io_threads_active == 1);
    for (int j = 1; j < server.io_threads_num; j++)
        pthread_mutex_lock(&io_threads_mutex[j]);
    server.io_threads_active = 0;
}

從上面的代碼中可以看出:

  • 開啟多線程IO是通過釋放mutex鎖來讓IO線程開始執(zhí)行讀數(shù)據(jù)或者寫數(shù)據(jù)動(dòng)作。
  • 暫停多線程IO則是通過加鎖來讓IO線程暫時(shí)不執(zhí)行讀數(shù)據(jù)或者寫數(shù)據(jù)動(dòng)作,此處加鎖后,IO線程主函數(shù)由于無法獲取到鎖,因此會(huì)暫時(shí)阻塞。

四、性能對(duì)比

測(cè)試環(huán)境

兩臺(tái)物理機(jī)配置:CentOS Linux release 7.3.1611(Core) ,12核CPU1.5GHz,256G內(nèi)存(free 128G)。

Redis版本

使用Redis6.0.6,多線程IO模式使用線程數(shù)量為4,即 io-threads 4 ,參數(shù) io-threads-do-reads 分別設(shè)置為 no 和 yes ,進(jìn)行對(duì)比測(cè)試。

壓測(cè)命令

redis-benchmark -h 172.xx.xx.xx -t set,get -n 1000000 -r 100000000 --threads ${threadsize} -d ${datasize} -c ${clientsize}


單線程 threadsize 為 1,多線程 threadsize 為 4
datasize為value 大小,分別設(shè)置為 128/512/1024
clientsize 為客戶端數(shù)量,分別設(shè)置為 256/2000
如:./redis-benchmark -h 172.xx.xx.xx -t set,get -n 1000000 -r 100000000 --threads 4 -d 1024 -c 256

統(tǒng)計(jì)結(jié)果

當(dāng) io-threads-do-reads 為 no 時(shí),統(tǒng)計(jì)圖表如下所示(c 2000表示客戶端數(shù)量為2000)。

圖片圖片

當(dāng) io-threads-do-reads 為 yes 時(shí),統(tǒng)計(jì)圖表如下所示(c 256表示客戶端數(shù)量為256)。

圖片圖片

結(jié)論

使用redis-benchmark做Redis6單線程和多線程簡(jiǎn)單SET/GET命令性能測(cè)試:

  1. 從上面可以看到GET/SET命令在設(shè)置4個(gè)IO線程時(shí),QPS相比于大部分情況下的單線程,性能幾乎是翻倍了。
  2. 連接數(shù)越多,多線程優(yōu)勢(shì)越明顯。
  3. value值越小,多線程優(yōu)勢(shì)越明顯。
  4. 使用多線程讀命令比寫命令優(yōu)勢(shì)更加明顯,當(dāng)value越大,寫命令越發(fā)沒有明顯的優(yōu)勢(shì)。
  5. 參數(shù) io-threads-do-reads 為yes,性能有微弱的優(yōu)勢(shì),不是很明顯。
  6. 總體來說,以上結(jié)果基本符合預(yù)期,結(jié)果僅作參考。

五、6.0 多線程 IO 不足

盡管引入多線程IO大幅提升了Redis性能,但是Redis6.0的多線程IO仍然存在一些不足:

  • CPU核心利用率不足:當(dāng)前主線程仍負(fù)責(zé)大部分的IO相關(guān)任務(wù),并且當(dāng)主線程處理客戶端的命令時(shí),IO線程會(huì)空閑相當(dāng)長(zhǎng)的時(shí)間,同時(shí)值得注意的是,主線程在執(zhí)行IO相關(guān)任務(wù)期間,性能受到最慢IO線程速度的限制。
  • IO線程執(zhí)行的任務(wù)有限:目前,由于主線程同步等待IO線程,線程僅執(zhí)行讀取解析和寫入操作。如果線程可以異步工作,我們可以將更多工作卸載到IO線程上,從而減少主線程的負(fù)載。
  • 不支持帶有TLS的IO線程。

最新的Valkey8.0版本中,通過引入異步IO線程,將更多的工作轉(zhuǎn)移到IO線程執(zhí)行,同時(shí)通過批量預(yù)讀取內(nèi)存數(shù)據(jù)減少內(nèi)存訪問延遲,大幅提高Valkey單節(jié)點(diǎn)訪問QPS,單個(gè)實(shí)例每秒可處理100萬個(gè)請(qǐng)求。我們后續(xù)再詳細(xì)介紹Valkey8.0異步IO特性。

六、總結(jié)

Redis6.0引入多線程IO,但多線程部分只是用來處理網(wǎng)絡(luò)數(shù)據(jù)的讀寫和協(xié)議解析,執(zhí)行命令仍然是單線程。通過開啟多線程IO,并設(shè)置合適的CPU數(shù)量,可以提升訪問請(qǐng)求一倍以上。

Redis6.0多線程IO仍然存在一些不足,沒有充分利用CPU核心,在最新的Valkey8.0版本中,引入異步IO將進(jìn)一步大幅提升Valkey性能。

責(zé)任編輯:武曉燕 來源: 得物技術(shù)
相關(guān)推薦

2022-01-04 11:11:32

Redis單線程Reactor

2025-04-24 08:15:00

Redis單線程線程

2019-06-17 14:20:51

Redis數(shù)據(jù)庫Java

2010-01-28 16:45:44

Android單線程模

2019-05-06 11:12:18

Redis高并發(fā)單線程

2009-07-10 09:05:20

SwingWorker

2021-08-10 07:00:01

Redis單線程并發(fā)

2019-11-25 10:13:52

Redis單線程I

2024-09-27 11:51:33

Redis多線程單線程

2022-07-18 13:59:43

Redis單線程進(jìn)程

2020-10-26 08:55:52

Redis單線程模型

2020-10-30 16:20:38

Redis單線程高并發(fā)

2023-10-15 12:23:10

單線程Redis

2020-06-11 09:35:39

Redis單線程Java

2020-11-09 09:33:37

多線程

2023-08-17 14:12:17

2023-03-21 08:02:36

Redis6.0IO多線程

2010-08-30 08:55:56

JavaScript引

2021-06-11 11:28:22

多線程fork單線程

2023-12-01 08:18:24

Redis網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

中文字幕成人动漫| 蜜臀av无码一区二区三区| 91麻豆国产视频| 欧美精品成人| 亚洲美腿欧美激情另类| 亚洲人视频在线| 俄罗斯一级**毛片在线播放| xnxx国产精品| 亚洲a级在线观看| 日本中文字幕免费观看| 日本精品黄色| 亚洲第一中文字幕| 欧美女同在线观看| cao在线视频| 亚洲色图在线看| 免费成人看片网址| 亚洲AV无码精品色毛片浪潮| 日日嗨av一区二区三区四区| 久久99久久99精品中文字幕| 人与嘼交av免费| 欧美三级自拍| 日韩女优视频免费观看| 91香蕉视频导航| h片在线观看视频免费免费| 国产精品高清亚洲| 欧美日本亚洲| 五月婷中文字幕| 国产精品正在播放| 国产精品欧美日韩久久| 1级黄色大片儿| 韩国在线视频一区| 操91在线视频| 香蕉成人在线视频| 欧洲grand老妇人| 亚洲免费一在线| 三级视频网站在线观看| 日韩成人18| 欧美精选在线播放| 亚洲天堂2018av| 欧美日韩免费看片| 欧美日韩一区二区精品| 丁香六月激情网| 日本在线视频网址| 亚洲免费三区一区二区| 成人短视频在线看| 九七电影韩国女主播在线观看| 国产精品网曝门| 婷婷四房综合激情五月| 成人18在线| 国产日韩v精品一区二区| 女同一区二区| 国自产拍在线网站网址视频| wwwwww.欧美系列| 久久综合福利| 美国一级片在线免费观看视频| av电影在线观看不卡| 精品一区二区三区自拍图片区 | 国产超级va在线视频| 欧美高清一级片在线观看| 日本一区二区三区四区在线观看 | 久久免费视频网| 久久久久99精品成人片毛片| 欧美三区不卡| 国内久久久精品| 久久精品久久国产| 亚洲国内自拍| 57pao国产成人免费| 久久99国产综合精品免费| 免播放器亚洲| 国产精品流白浆视频| 在线观看视频二区| 国产精一品亚洲二区在线视频| 91九色在线观看| 天天干天天插天天操| 久久综合色之久久综合| 亚洲啪啪av| 成年人黄视频在线观看| 亚洲大片在线观看| 成年网站在线免费观看| 欧美成人毛片| 精品三级在线观看| 国产网站无遮挡| 99久久婷婷| 午夜精品久久17c| 真实新婚偷拍xxxxx| 国产一区视频导航| 久久www免费人成精品| 成人免费在线电影| 一区二区三区在线高清| 日韩av综合在线观看| 免费成人毛片| 精品国产91亚洲一区二区三区婷婷| 欧美性xxxx图片| 91一区二区三区四区| 韩国欧美亚洲国产| 最近日韩免费视频| 9i在线看片成人免费| 亚洲欧洲日本国产| 久久男人天堂| 欧美一个色资源| 一区二区精品免费| 国产精品草草| 国产精品在线看| 色婷婷综合视频| 中文字幕一区av| 国内外成人免费激情视频| 视频欧美精品| 亚洲人成电影网站色xx| 久久久久久欧美精品se一二三四| 日本午夜精品视频在线观看 | 日韩a一级欧美一级| 亚洲成aⅴ人片久久青草影院| 久久久极品av| 波多野结衣激情视频| 成人sese在线| 青春草在线视频免费观看| 在线天堂资源www在线污| 日韩午夜激情免费电影| 黄色国产在线播放| 欧美在线综合| 精品国产乱码一区二区三区四区| 18+激情视频在线| 欧美日韩日日夜夜| 免费人成又黄又爽又色| 最新亚洲视频| 国产高清自拍一区| 蜜桃视频网站在线观看| 欧美一a一片一级一片| 欧美无人区码suv| 亚洲小说欧美另类社区| 亚洲a级在线播放观看| 在线观看麻豆| 欧美无乱码久久久免费午夜一区| 中日韩精品一区二区三区| 精品不卡视频| 国产经品一区二区| 9191在线播放| 日韩午夜激情电影| 天天看片中文字幕| 国产高清不卡一区| 成人黄色片免费| 亚洲精品aⅴ| 九色精品美女在线| 国产丰满美女做爰| 亚洲精品成人悠悠色影视| 精品国产鲁一鲁一区二区三区| 久久综合国产| 91欧美激情另类亚洲| 精品人妻无码中文字幕18禁| 日韩中文一区二区| 欧美日韩国产成人在线观看| 国产色在线视频| 一区二区三区在线视频免费观看| 性久久久久久久久久久久久久| 五月天激情综合网| av一区二区三区在线观看| 亚洲区欧洲区| 亚洲成成品网站| 欧美一二三区视频| 久久免费看少妇高潮| 一道本视频在线观看| 久久电影院7| 91色视频在线观看| 国产又色又爽又黄刺激在线视频| 精品国产91洋老外米糕| 在线免费黄色av| 国产精品亲子乱子伦xxxx裸| 亚洲精品国产久| 伊人久久亚洲影院| 鲁丝一区鲁丝二区鲁丝三区| 日本.亚洲电影| 久久艹在线视频| 内射后入在线观看一区| 欧美性猛交xxxx黑人猛交| 成年人在线免费看片| 激情五月婷婷综合| 一卡二卡三卡视频| 国产精品入口久久| 91欧美精品成人综合在线观看| 国产一线二线在线观看| 亚洲天堂av在线免费观看| 91亚洲国产成人久久精品麻豆| 一区二区三区在线影院| 国产人妻一区二区| 国产一区二区三区高清播放| 日韩免费视频播放| 日韩一区二区在线免费| 国产高清在线一区二区| 成人自拍视频网| 欧美激情国内偷拍| 国产h在线观看| 精品少妇一区二区三区在线播放| 在线永久看片免费的视频| 亚洲欧洲日韩一区二区三区| 中文在线永久免费观看| 国产真实乱偷精品视频免| 男女激情无遮挡| 久久中文视频| 欧美日韩国产不卡在线看| 日韩在线观看一区二区三区| 日本免费一区二区三区视频观看| 精品欧美色视频网站在线观看| 日韩经典中文字幕| 精品国自产拍在线观看| 欧美在线你懂的| 欧美亚洲天堂网| 国产精品丝袜91| 免费中文字幕av| 国产风韵犹存在线视精品| 草草草在线视频| 亚洲国产国产亚洲一二三 | 亚洲欧洲日韩| 日本免费一区二区三区| 99香蕉久久| 成人网欧美在线视频| 婷婷激情一区| 91精品国产乱码久久久久久久久| 快射av在线播放一区| 国产亚洲成av人片在线观看桃| 亚洲欧美黄色片| 日韩一区二区视频| 11024精品一区二区三区日韩| 色综合天天狠狠| 日韩精品视频播放| 亚洲一区二区三区不卡国产欧美| 夫妻性生活毛片| 国产精品久久久久久福利一牛影视 | 国产剧情一区二区在线观看| 国产精品嫩草视频| 一区二区三区四区日本视频| 久久久久久久色| 天堂av资源在线观看| 久久成年人免费电影| 黄色成年人视频在线观看| 色琪琪综合男人的天堂aⅴ视频| 国产精品久久久久一区二区国产| 日韩精品黄色网| 午夜福利一区二区三区| 亚洲国产三级网| 蜜桃av中文字幕| 亚洲第一天堂无码专区| 刘玥91精选国产在线观看| 欧美一区二区视频在线观看2020| 中文字幕一区二区三区免费看| 在线视频国内自拍亚洲视频| 区一区二在线观看| 欧美日韩亚洲一区二区| 国产精品免费精品一区| 日本韩国欧美一区二区三区| 国产精品尤物视频| 欧美午夜一区二区| 美女黄页在线观看| 欧美群妇大交群中文字幕| 亚洲系列在线观看| 欧美精品自拍偷拍| 精品国产va久久久久久久| 日韩精品在线一区二区| 免费看黄网站在线观看| 亚洲精品久久视频| 天堂v视频永久在线播放| 亚洲天堂色网站| 波多野结衣在线影院| 精品国模在线视频| 亚洲资源一区| 97超级碰在线看视频免费在线看| 黑人巨大精品欧美一区二区桃花岛| 国产精品igao视频| 成人免费观看49www在线观看| 444亚洲人体| 理论片一区二区在线| 欧美日韩一区二区三区在线视频 | 韩国av电影在线观看| 亚洲精品720p| 国产福利第一视频在线播放| 久久天天躁狠狠躁老女人| 丁香花视频在线观看| 欧美专区在线播放| 欧美亚洲黄色| 国产一区福利视频| 精品产国自在拍| 9色视频在线观看| 亚洲永久免费| 手机免费av片| 99re6这里只有精品视频在线观看| 国产ts在线播放| 亚洲欧美另类久久久精品| 国内免费精品视频| 欧美日韩视频在线一区二区| 亚洲国产精品一| 国产一区二区三区欧美| 香蕉成人app免费看片| 国产激情视频一区| 亚洲精品观看| 日韩视频在线观看国产| 国内精品福利| 亚洲欧美久久久久| a在线欧美一区| 天天做夜夜爱爱爱| 欧美性猛交xxxx免费看| 精品欧美一区二区精品少妇| 亚洲图片欧美午夜| 搞黄网站在线看| 国产在线精品成人一区二区三区| 鲁大师精品99久久久| 中文字幕一区二区三区乱码| 香蕉久久夜色精品| 国产chinesehd精品露脸| 中文字幕第一区第二区| 久久草视频在线| 日韩欧美另类在线| 1024视频在线| 日韩av不卡在线| 欧美91在线| 日韩在线观看a| 国内一区二区在线| 人妻熟人中文字幕一区二区| 精品日本美女福利在线观看| 午夜精品久久久久久久91蜜桃| 尤物九九久久国产精品的分类| 高清在线视频不卡| 成人网页在线免费观看| 日产精品一区二区| 黑森林福利视频导航| 不卡视频免费播放| 国产盗摄一区二区三区在线| 欧美日韩国产高清一区二区三区 | 性chinese极品按摩| 国产亚洲欧美一区在线观看| 国产精品久久久免费视频| 亚洲精品一区二区精华| 日本在线观看高清完整版| 91免费欧美精品| 91精品国产91久久久久久密臀| 亚洲一级片免费| 国产精品热久久久久夜色精品三区| 波多野结衣不卡| 亚洲欧美中文另类| 原纱央莉成人av片| 精品在线视频一区二区| 亚洲国产91| 国产麻豆剧传媒精品国产av| 亚洲国产精品影院| 全国男人的天堂网| 97精品视频在线播放| 久久国产精品色av免费看| 青青草视频在线视频| 国产成人精品午夜视频免费| 久久亚洲av午夜福利精品一区| 日韩精品在线看片z| 男插女视频久久久| 国产一区免费在线| 国产视频一区在线观看一区免费| 97人妻精品一区二区三区免| 一本一道波多野结衣一区二区 | 欧美福利视频在线| 视频一区日韩| 免费成人午夜视频| 久久九九国产精品| 一级黄色大毛片| 伦理中文字幕亚洲| 成人h动漫免费观看网站| 久无码久无码av无码| 99re热视频这里只精品| 欧美一区二区三区久久久| 中文字幕在线亚洲| 另类视频一区二区三区| 成人免费在线网| 国产三级精品三级在线专区| 一区二区视频在线免费观看| 欧美老女人性视频| 久久久久久毛片免费看 | 亚洲成人偷拍| 人妻熟妇乱又伦精品视频| 国产午夜精品理论片a级大结局| 国产精品久久久久久久成人午夜 | 香蕉免费毛片视频| 国产一区二区三区久久精品| 国产精品一区二区精品| 久久综合色视频| 一区免费观看视频| 天堂成人在线视频| 国产精品三级美女白浆呻吟| 国产精品s色| 三年中国中文观看免费播放| 日韩三级在线观看| 韩日精品一区二区| 成人国产在线看| 日本一区二区视频在线观看| 性生活视频软件| 国产精品免费看久久久香蕉| 一区视频在线| 人与动物性xxxx| 精品在线欧美视频| 日本久久伊人| 在线免费观看av的网站| 亚洲aⅴ怡春院| av在线app| 日韩电影在线播放| av中文字幕一区| 国产视频www|