redis IO多路复用源码分析

推荐大家可以先去看Redis 源码分析 I/O 模型详解,下面有些图我是复制这里面的,自己再画有点重构造轮子

1、首先说说IO多路复用在整个请求链路中所在的位置

用必应搜到的图,
在这里插入图片描述
redis的多路复用相当于这张图中的selector(多路复用器),可以有效利用空闲线程,处理读写请求。
是在服务端部署。

2、IO多路复用选择哪个实现的的源码(在ae.c中)

在这里插入图片描述

/* Include the best multiplexing layer supported by this system.
 * The following should be ordered by performances, descending. */
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
    #ifdef HAVE_EPOLL
    #include "ae_epoll.c"
    #else
        #ifdef HAVE_KQUEUE
        #include "ae_kqueue.c"
        #else
        #include "ae_select.c"
        #endif
    #endif
#endif

优先级evport->epoll->kqueue->select。其执行的效率也是从高到低 效率也是从高到低,而到底如何选不是人工指定,而是由服务器所部署在哪种服务器上决定,下面列出

  1. Solaries 10 中的 evport
  2. Linux 中的 epoll
  3. macOS/FreeBSD 中的 kqueue,
  4. select 为最后的默认选项,但是它也是性能最差的

select比其他差的原因是

  1. 前三个首先都是O(1)的复杂度,最后一个为O(n),
  2. 前三个的实现函数(第三部分有描述)都使用了特定内核内部的结构,并且能够服务几十万的文件描述符,而select在使用时会扫描全部监听的描述符,并且只能同时服务 1024 个文件描述符

3、模块方法含义解释

之后比较一下各个模块的方法
在这里插入图片描述
四个模块都含有的方法是下面这些,相当于模板设计模式,根据部署环境选择不同的实现。

static int aeApiCreate(aeEventLoop *eventLoop)
static int aeApiResize(aeEventLoop *eventLoop, int setsize)
static void aeApiFree(aeEventLoop *eventLoop) 
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)
static char *aeApiName(void)

虽然都有这些方法,但是内部细节会有很多不同,详细的介绍看下面的链接
redis中io复用之evport
redis中io复用之epoll
redis中io复用之kqueue
redis中io复用之select

4、6.0版本以后增加了多线程

如果你看过初始化服务器的源代码的话,在main方法的最后有一个InitServerLast 方法,

/* Some steps in server initialization need to be done last (after modules
 * are loaded).服务器初始化中的一些步骤需要最后完成(在加载模块 之后)。
 * Specifically, creation of threads due to a race bug in ld.so, in which
 * Thread Local Storage initialization collides with dlopen call.
 * see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329
 具体来说,由于 ld.so 中的竞争错误而创建线程,其中Thread Local Storage 初始化与 dlopen 调用发生冲突。
  见:https://sourceware.org/bugzilla/show_bug.cgi?id=19329
 */
void InitServerLast() {
    bioInit();
    initThreadedIO();
    set_jemalloc_bg_thread(server.jemalloc_bg_thread);
    server.initial_memory_usage = zmalloc_used_memory();
}
/* Initialize the data structures needed for threaded I/O.
 初始化线程 I/O 所需的数据结构*/
void initThreadedIO(void) {
    server.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.
     如果用户选择了单个线程,则不要生成任何线程:我们将直接从主线程处理 I/O*/
    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.产生并初始化 I/O 线程 */
    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. 线程 0 是主线程*/

        /* Things we do only for the additional threads.我们只为附加线程做的事情 */
        pthread_t tid;
        pthread_mutex_init(&io_threads_mutex[i],NULL);
        setIOPendingCount(i, 0);//设置 IO 挂起计数
        初始化后将线程暂时锁住
        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;
    }
}

这里就不很详细的深入了,想了解这方面的可以看一下Redis6.0多线程

但是需要注意的是:虽然是加了多线程,但是实际命令的执行还是单线程(队列) 的形式(每个线程执行readQueryFromClient,将对应的请求放入一个队列中,单线程执行),主要是把client到输入缓冲区和结果到输出缓冲区这两段用的是多线程,原因有两方面考虑:

  1. 单线程实际执行可以避免处理锁和竞争的问题
  2. 并且读写缓冲区的时间IO远远大于命令实际执行的时间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值