【Redis 源码】Redis 启动过程分析

小令童鞋 梅干菜你个小酥饼哦。 本文由博客端 https://www.zeekling.cn 主动推送
本贴最后更新于 333 天前,其中的信息可能已经沧海桑田

简介

由于本人目前是华为 FusionInsight HD 中 Redis 组件的 Owner,所以要对 Redis 进行深入的了解,这对于 C 语言水平不咋地的我来讲还是有点难度的,于是我决定先从 Redis 的启动开始看,了解其基本原理。

配置初始化

Redis 服务启动首先做的第一步就是初始化配置。Redis 初始化配置主要包括初始化命令表和加载配置两部分。

初始化 ACL 权限信息

主要是通过加载 redis.conf 配置文件里面的配置信息,用于控制登录用户执行命令的权限,仔细详见:Redis 6 ACL 源码详解

初始化命令表

在函数 populateCommandTable 将 redisCommandTable 中的命令加载到字典 server.commands 当中,用于执行命令的时候使用,并且对于比较常用的命令赋予成员属性,减少查找。redisCommandTable 的结构如下:

struct redisCommand redisCommandTable[] = {
    {"module",moduleCommand,-2,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},

    {"get",getCommand,2,
     "read-only fast @string",
     0,NULL,1,1,1,0,0,0},

    /* Note that we can't flag set as fast, since it may perform an
     * implicit DEL of a large key. */
    {"set",setCommand,-3,
     "write use-memory @string",
     0,NULL,1,1,1,0,0,0},

    {"setnx",setnxCommand,3,
     "write use-memory fast @string",
     0,NULL,1,1,1,0,0,0},
}

防止查找的命令如下:


    server.delCommand = lookupCommandByCString("del");
    server.multiCommand = lookupCommandByCString("multi");
    server.lpushCommand = lookupCommandByCString("lpush");
    server.lpopCommand = lookupCommandByCString("lpop");
    server.rpopCommand = lookupCommandByCString("rpop");
    server.zpopminCommand = lookupCommandByCString("zpopmin");
    server.zpopmaxCommand = lookupCommandByCString("zpopmax");
    server.sremCommand = lookupCommandByCString("srem");
    server.execCommand = lookupCommandByCString("exec");
    server.expireCommand = lookupCommandByCString("expire");
    server.pexpireCommand = lookupCommandByCString("pexpire");
    server.xclaimCommand = lookupCommandByCString("xclaim");
    server.xgroupCommand = lookupCommandByCString("xgroup");
    server.rpoplpushCommand = lookupCommandByCString("rpoplpush");

初始化哨兵模式

当开启哨兵模式时,redis 启动就会初始化哨兵模式相关参数等。初始化哨兵模式主要是函数 initSentinelConfig() 和 initSentinel()两部分。

struct redisCommand sentinelcmds[] = { 
      {"ping",pingCommand,1,"",0,NULL,0,0,0,0,0}, 
      {"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},   
      {"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},   
      {"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0}, 
      {"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},  
      {"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},  
      {"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},  
      {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}, 
      {"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0},
      {"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0}, 
      {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}, 
      {"auth",authCommand,2,"no-auth no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
      {"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0}  
}

检查 RDB/AOF 文件

启动前会会通过函数 redis_check_rdb_main()/redis_check_aof_main()检查 RDB/AOF 文件的完整性。

命令行参数处理

如果是简单的参数例如-v 或--version、-h 或--help,就会直接调用相应的方法,打印信息。如果是使用其他配置文件,则修改 server.exec_argv。对于其他信息,会将他们转换成字符串,然后添加进配置文件,例如“--port 6380”就会被转换成“port 6380\n”加进配置文件。这时,redis 就会调用 loadServerConfig()函数来加载配置文件,这个过程会覆盖掉前面初始化默认配置文件的变量的值。

初始化 Redis

初始化 Shared object

createSharedObjects()函数会创建一些 shared 对象保存在全局的 shared 变量中,对于不同的命令,可能会有相同的返回值(比如报错)。这样在返回时就不必每次都去新增对象了,保存到内存中了。这个设计就是以 Redis 启动时多消耗一些时间为代价,换取运行的更小的延迟。

初始化监听事件

initServer()函数调用 aeCreateEventLoop()函数(ae.c 文件)来增加循环事件,并将结果返回给 server 的 el 成员。Redis 使用不同的函数来兼容各个平台,在 Linux 平台使用 epoll,在 BSD 使用 kqueue,都不是的话,最终会使用 select。Redis 轮询新的连接以及 I/O 事件,有新的事件到来时就会及时作出响应。

监听端口

anetUnixServer()函数中使用函数 anetListen 监听端口。

初始化 LRU 键池

evictionPoolAlloc 函数用于初始化 LRU 的键池,Redis 的 key 过期策略是近似 LRU 算法

void evictionPoolAlloc(void) {
    struct evictionPoolEntry *ep;
    int j;

    ep = zmalloc(sizeof(*ep)*EVPOOL_SIZE);
    for (j = 0; j < EVPOOL_SIZE; j++) {
        ep[j].idle = 0;
        ep[j].key = NULL;
        ep[j].cached = sdsnewlen(NULL,EVPOOL_CACHED_SDS_SIZE);
        ep[j].dbid = 0;
    }
    EvictionPoolLRU = ep;
}


定时任务

Redis 会执行 aeCreateTimeEvent()(在 ae.c 文件中)函数,用来新建一个循环执行 serverCron()函数的事件。serverCron()默认每 100 毫秒执行一次。

 /* Create the timer callback, this is our way to process many background
  * operations incrementally, like clients timeout, eviction of unaccessed
  * expired keys and so forth. */
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
    serverPanic("Can't create event loop timers.");
    exit(1);
}

主循环事件

程序调用 aeMain()函数,进入主循环,这时其他的一些循环事件也会分别被调用。

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}

到此 Redis 就启动完成了。

  • 代码
    447 引用 • 545 回帖 • 6 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    250 引用 • 244 回帖 • 567 关注
  • 原创
    9 引用 • 44 回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...