Redis服务器负责接管解决用户申请,为用户提供服务。
Redis服务器的启动命令格局如下:

redis-server [ configfile ] [ options ]

configfile参数指定配置文件。options参数指定启动配置项,它能够笼罩配置文件中的配置项,如

redis-server /path/to/redis.conf --port 7777 --protected-mode no

该命令启动Redis服务,并指定了配置文件/path/to/redis.conf,给出了两个启动配置项:port、protected-mode。

本文通过浏览Redis源码,剖析Redis启动过程,内容摘自新书《Redis外围原理与实际》。
本文波及Redis的很多概念,如事件循环器、ACL、Module、LUA、慢日志等,这些性能在作者新书《Redis外围原理与实际》做了详尽剖析,感兴趣的读者能够参考本书。

服务器定义

提醒:本章代码如无非凡阐明,均在server.h、server.c中。

Redis中定义了server.h/redisServer构造体,存储Redis服务器信息,包含服务器配置项和运行时数据(如网络连接信息、数据库redisDb、命令表、客户端信息、从服务器信息、统计信息等数据)。

struct redisServer {    pid_t pid;                      pthread_t main_thread_id;             char *configfile;               char *executable;               char **exec_argv;        ...}

redisServer中的属性很多,这里不一一列举,等到剖析具体性能时再阐明相干的server属性。
server.h中定义了一个redisServer全局变量:

extern struct redisServer server;

本书说到的server变量,如无非凡阐明,都是指该redisServer全局变量。例如,第1局部说过server.list_max_ziplist_size等属性,正是指该变量的属性。
能够应用INFO命令获取服务器的信息,该命令次要返回以下信息:

  • server:无关Redis服务器的惯例信息。
  • clients:客户端连贯信息。
  • memory:内存耗费相干信息。
  • persistence:RDB和AOF长久化信息。
  • stats:惯例统计信息。
  • replication:主/正本复制信息。
  • cpu:CPU耗费信息。
  • commandstats:Redis 命令统计信息。
  • cluster:Redis Cluster集群信息。
  • modules:Modules模块信息。
  • keyspace:数据库相干的统计信息。
  • errorstats:Redis谬误统计信息。

INFO命令响应内容中除了memory和cpu等统计数据,其余数据大部分都保留在redisServer中。

main函数

server.c/main函数负责启动Redis服务:

int main(int argc, char **argv) {    ...    // [1]    server.sentinel_mode = checkForSentinelMode(argc,argv);    // [2]    initServerConfig();    ACLInit();         moduleInitModulesSystem();    tlsInit();    // [3]    server.executable = getAbsolutePath(argv[0]);    server.exec_argv = zmalloc(sizeof(char*)*(argc+1));    server.exec_argv[argc] = NULL;    for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);    // [4]    if (server.sentinel_mode) {        initSentinelConfig();        initSentinel();    }    // [5]    if (strstr(argv[0],"redis-check-rdb") != NULL)        redis_check_rdb_main(argc,argv,NULL);    else if (strstr(argv[0],"redis-check-aof") != NULL)        redis_check_aof_main(argc,argv);    // more}

【1】查看该Redis服务器是否以sentinel模式启动。
【2】initServerConfig函数将redisServer中记录配置项的属性初始化为默认值。ACLInit函数初始化ACL机制,moduleInitModulesSystem函数初始化Module机制。
【3】记录Redis程序可执行门路及启动参数,以便后续重启服务器。
【4】如果以Sentinel模式启动,则初始化Sentinel机制。
【5】如果启动程序是redis-check-rdb或redis-check-aof,则执行redis_check_rdb_main或redis_check_aof_main函数,它们尝试测验并修复RDB、AOF文件后便退出程序。
Redis编译实现后,会生成5个可执行程序:

  • redis-server:Redis执行程序。
  • redis-sentinel:Redis Sentinel执行程序。
  • redis-cli:Redis客户端程序。
  • redis-benchmark:Redis性能压测工具。
  • redis-check-aof、redis-check-rdb:用于测验和修复RDB、AOF长久化文件的工具。

持续剖析main函数:

int main(int argc, char **argv) {    ...    if (argc >= 2) {        j = 1;         sds options = sdsempty();        char *configfile = NULL;        // [6]        if (strcmp(argv[1], "-v") == 0 ||            strcmp(argv[1], "--version") == 0) version();        ...        // [7]        if (argv[j][0] != '-' || argv[j][1] != '-') {            configfile = argv[j];            server.configfile = getAbsolutePath(configfile);            zfree(server.exec_argv[j]);            server.exec_argv[j] = zstrdup(server.configfile);            j++;        }       // [8]        while(j != argc) {            ...        }        // [9]        if (server.sentinel_mode && configfile && *configfile == '-') {            ...            exit(1);        }        // [10]        resetServerSaveParams();        loadServerConfig(configfile,options);        sdsfree(options);    }    ...}

【6】对-v、--version、--help、-h、--test-memory等命令进行优先解决。
strcmp函数比拟两个字符串str1、str2,若str1=str2,则返回零;若str1<str2,则返回正数;若str1>str2,则返回负数。
【7】如果启动命令的第二个参数不是以“--”开始的,则是配置文件参数,将配置文件门路转化为绝对路径,存入server.configfile中。
【8】读取启动命令中的启动配置项,并将它们拼接到一个字符串中。
【9】以Sentinel模式启动,必须指定配置文件,否则间接报错退出。
【10】config.c/resetServerSaveParams函数重置server.saveparams属性(该属性寄存RDB SAVE配置)。config.c/loadServerConfig函数从配置文件中加载所有配置项,并应用启动命令配置项笼罩配置文件中的配置项。

提醒:config.c中的configs数组定义了大多数配置选项与server属性的对应关系:

standardConfig configs[] = {    createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),    createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL),    ...}

配置项rdbchecksum对应server.rdb_checksum属性,默认值为1(即bool值yes),其余配置项以此类推。如果读者须要查找配置项对应的server属性和默认值,则能够从中查找。

上面持续剖析main函数:

int main(int argc, char **argv) {    ...    // [11]        server.supervised = redisIsSupervised(server.supervised_mode);    int background = server.daemonize && !server.supervised;    if (background) daemonize();    // [12]    serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");    ...        // [13]    initServer();    if (background || server.pidfile) createPidFile();    ...    if (!server.sentinel_mode) {        ...        // [14]        moduleLoadFromQueue();        ACLLoadUsersAtStartup();        InitServerLast();        loadDataFromDisk();        if (server.cluster_enabled) {            if (verifyClusterConfigWithData() == C_ERR) {                ...                exit(1);            }        }        ...    } else {        // [15]        InitServerLast();        sentinelIsRunning();        ...    }    ...    // [16]    redisSetCpuAffinity(server.server_cpulist);    setOOMScoreAdj(-1);    // [17]    aeMain(server.el);    // [18]    aeDeleteEventLoop(server.el);    return 0;}

【11】server.supervised属性指定是否以upstart服务或systemd服务启动Redis。如果配置了server.daemonize且没有配置server.supervised,则以守护过程的形式启动Redis。
【12】打印启动日志。
【13】initServer函数初始化Redis运行时数据,createPidFile函数创立pid文件。
【14】如果非Sentinel模式启动,则实现以下操作:
(1)moduleLoadFromQueue函数加载配置文件指定的Module模块;
(2)ACLLoadUsersAtStartup函数加载ACL用户管制列表;
(3)InitServerLast函数负责创立后盾线程、I/O线程,该步骤需在Module模块加载后再执行;
(4)loadDataFromDisk函数从磁盘中加载AOF或RDB文件。
(5)如果以Cluster模式启动,那么还须要验证加载的数据是否正确。
【15】如果以Sentinel模式启动,则调用sentinelIsRunning函数启动Sentinel机制。
【16】尽可能将Redis主线程绑定到server.server_cpulist配置的CPU列表上,Redis 4开始应用多线程,该操作能够缩小不必要的线程切换,进步性能。
【17】启动事件循环器。事件循环器是Redis中的重要组件。在Redis运行期间,由事件循环器提供服务。
【18】执行到这里,阐明Redis服务已进行,aeDeleteEventLoop函数革除事件循环器中的事件,最初退出程序。

Redis初始化过程

上面看一下initServer函数,它负责初始化Redis运行时数据:

void initServer(void) {    int j;    // [1]    signal(SIGHUP, SIG_IGN);    signal(SIGPIPE, SIG_IGN);    setupSignalHandlers();    // [2]    makeThreadKillable();    // [3]    if (server.syslog_enabled) {        openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,            server.syslog_facility);    }    // [4]    server.aof_state = server.aof_enabled ? AOF_ON : AOF_OFF;    server.hz = server.config_hz;    server.pid = getpid();    ...        // [5]    createSharedObjects();    adjustOpenFilesLimit();    // [6]    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);    if (server.el == NULL) {        ...        exit(1);    }        // more}

【1】设置UNIX信号处理函数,使Redis服务器收到SIGINT信号后退出程序。
【2】设置线程随时响应CANCEL信号,终止线程,以便进行程序。
【3】如果开启了Unix系统日志,则调用openlog函数与Unix系统日志建设输入连贯,以便输入系统日志。
【4】初始化server中负责存储运行时数据的相干属性。
【5】createSharedObjects函数创立共享数据集,这些数据可在各场景中共享应用,如小数字0~9999、罕用字符串+OK\r\n(命令解决胜利响应字符串)、+PONG\r\n(ping命令响应字符串)。adjustOpenFilesLimit函数尝试批改环境变量,进步零碎容许关上的文件描述符下限,防止因为大量客户端连贯(Socket文件描述符)导致谬误。
【6】创立事件循环器。
UNIX编程:信号也称为软中断,信号是UNIX提供的一种解决异步事件的办法,程序通过设置回调函数通知零碎内核,在信号产生后要做什么操作。零碎中很多场景会产生信号,例如:

  • 用户按下某些终端键,使终端产生信号。例如,用户在终端按下了中断键(个别为Ctrl+C组合键),会发送SIGINT信号告诉程序进行运行。
  • 零碎中产生了某些特定事件,例如,当alarm函数设置的定时器超时,内核发送SIGALRM信号,或者一个过程终止时,内核发送SIGCLD信号给其父过程。
  • 某些硬件异样,例如,除数为0、有效的内存援用。
  • 程序中应用函数发送信号,例如,调用kill函数将任意信号发送给另一个过程。
    感兴趣的读者能够自行深刻理解UNIX编程相干内容。

接着剖析initServer函数:

void initServer(void) {        server.db = zmalloc(sizeof(redisDb)*server.dbnum);    // [7]    if (server.port != 0 &&        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)        exit(1);    ...    // [8]    for (j = 0; j < server.dbnum; j++) {        server.db[j].dict = dictCreate(&dbDictType,NULL);        server.db[j].expires = dictCreate(&keyptrDictType,NULL);        ...    }    // [9]    evictionPoolAlloc();     server.pubsub_channels = dictCreate(&keylistDictType,NULL);    server.pubsub_patterns = listCreate();    ...}

【7】如果配置了server.port,则开启TCP Socket服务,接管用户申请。如果配置了server.tls_ port,则开启TLS Socket服务,Redis 6.0开始反对TLS连贯。如果配置了server.unixsocket,则开启UNIX Socket服务。如果下面3个选项都没有配置,则报错退出。
【8】初始化数据库server.db,用于存储数据。
【9】evictionPoolAlloc函数初始化LRU/LFU样本池,用于实现LRU/LFU近似算法。
持续初始化server中存储运行时数据的相干属性:

void initServer(void) {    ...    // [10]    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {        serverPanic("Can't create event loop timers.");        exit(1);    }    // [11]    for (j = 0; j < server.ipfd_count; j++) {        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,            acceptTcpHandler,NULL) == AE_ERR)            {                serverPanic(                    "Unrecoverable error creating server.ipfd file event.");            }    }    ...    // [12]    aeSetBeforeSleepProc(server.el,beforeSleep);    aeSetAfterSleepProc(server.el,afterSleep);    // [13]    if (server.aof_state == AOF_ON) {        server.aof_fd = open(server.aof_filename,                               O_WRONLY|O_APPEND|O_CREAT,0644);        ...    }    // [14]    if (server.arch_bits == 32 && server.maxmemory == 0) {        ...        server.maxmemory = 3072LL*(1024*1024); /* 3 GB */        server.maxmemory_policy = MAXMEMORY_NO_EVICTION;    }    // [15]    if (server.cluster_enabled) clusterInit();    replicationScriptCacheInit();    scriptingInit(1);    slowlogInit();    latencyMonitorInit();}

【10】创立一个工夫事件,执行函数为serverCron,负责解决Redis中的定时工作,如清理过期数据、生成RDB文件等。
【11】别离为TCP Socket、TSL Socks、UNIX Socket注册监听AE_READABLE类型的文件事件,事件处理函数别离为acceptTcpHandler、acceptTLSHandler、acceptUnixHandler,这些函数负责接管Socket中的新连贯,本书后续会详细分析acceptTcpHandler函数。
【12】注册事件循环器的钩子函数,事件循环器在每次阻塞前后都会调用钩子函数。
【13】如果开启了AOF,则事后关上AOF文件。
【14】如果Redis运行在32位操作系统上,因为32位操作系统内存空间限度为4GB,所以将Redis应用内存限度为3GB,防止Redis服务器因内存不足而解体。
【15】如果以Cluster模式启动,则调用clusterInit函数初始化Cluster机制。

  • replicationScriptCacheInit函数初始化server.repl_scriptcache_dict属性。
  • scriptingInit函数初始化LUA机制。
  • slowlogInit函数初始化慢日志机制。
  • latencyMonitorInit函数初始化提早监控机制。

总结:

  • redisServer构造体存储服务端配置项、运行时数据。
  • server.c/main是Redis启动办法,负责加载配置,初始化数据库,启动网络服务,创立并启动事件循环器。

文章最初,介绍一下新书《Redis外围原理与实际》,本书通过深入分析Redis 6.0源码,总结了Redis外围性能的设计与实现。通过浏览本书,读者能够深刻了解Redis外部机制及最新个性,并学习到Redis相干的数据结构与算法、Unix编程、存储系统设计,分布式系统架构等一系列常识。
通过该书编辑批准,我会持续在集体技术公众号(binecy)公布书中局部章节内容,作为书的预览内容,欢送大家查阅,谢谢。

语雀平台预览:《Redis外围原理与实际》
京东链接