Redis是如何对外提供服务的

2018-11-20  本文已影响0人  驶向灯塔的小船

      我们很多人都用过 Redis,自己肯定也搭建过 Redis 的环境,然后使用 set 命令设置一个值,设置完之后 Redis 会给你返回 "OK"。就这样我们 Redis 环境就搭建成功了,成功之后肯定有人会有这样的疑问,一个 ’set name marvel’ 命令到达 Redis 服务器的时候,服务器是如何返回’OK’ 的?里面命令处理的流程如何,具体细节怎么样?下面我们就来解决这些问题吧!
      获取我们换一种思路,Java 程序员都知道现在流行 SpringBoot 应用,那么我们时如何开发一个 SpringBoot 应用对外提供服务的呢?下面我就来分析一下吧。

  1. 我们要启动我们的 Application 类
  2. 通过启动日志我们会发现,SpringBoot 会去加载我们的类信息
  3. 加载我们的配置信息
  4. Tomcat 监听我们的 8080 端口,接收请求
  5. 给用户返回响应

      简单分析完 SpringBoot 应用,我们再来看看 Redis 是怎样提供服务的吧,首先肯定一点,Redis 启动肯定也有对呀的 Application 类,也就是 main 函数了啦,我们看看我们的 main 函数吧。

/**
 * todo: 服务端 main 方法
 *
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char **argv) {
    
    // ...
    /*
        We need to initialize our libraries, and the server configuration.
        我们需要初始化我们的库和服务器配置
    */
     //...
    // 设定默认的参数值
    initServerConfig();
    
    // 加载配置文件,也就是加载 redis.conf 文件
    loadServerConfig(configfile, options);

    /* 
     * 我们现在需要初始化sentinel来解析配置文件
     * 在哨兵模式中将具有填充哨兵的效果具有要监控的主节点的数据结构。
     * 如果我们是处于哨兵模式,那么就需要加载哨兵配置文件
     */
    if (server.sentinel_mode) {
        initSentinelConfig();
        initSentinel();
    }

    // ...

    // todo: 对 server 进行初始化
    initServer();
    
     // ...

    /* 警告用户可疑的maxmemory设置(用户可以设置内存大小) */
    if (server.maxmemory > 0 && server.maxmemory < 1024 * 1024) {
        serverLog(LL_WARNING,
                  "WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?",
                  server.maxmemory);
    }

    /*
     * 在进入事件循环之前,为服务器设置每次事件循环之前都要执行的一个函数 beforeSleep()
     * 该函数一开始就会执行集群的 clusterBeforeSleep() 函数
     */
    aeSetBeforeSleepProc(server.el, beforeSleep);
    aeSetAfterSleepProc(server.el, afterSleep);
    // 进入事件循环,一开始就会执行之前设置的 beforeSleep() 函数,之后就等待事件发生,处理就绪的事件。
    aeMain(server.el);
    // aeMain 里面会不断循环的等待事件的到来,直到遇到 stop 事件才会跳出循环
    // 然后执行删除事件
    aeDeleteEventLoop(server.el);
    return 0;
}

上面我只列出来一些我们熟悉的操作。下面我们就来看看他们分别做了什么?

initServerConfig

/**
 * 用于设置默认值
 * 下面我只介绍一些我们常用参数的默认值
 */
void initServerConfig(void) {
     ...
    // 默认端口号是 6379
    server.port = CONFIG_DEFAULT_SERVER_PORT;
    // 默认 tcp 监听的 backlog 为 511 个
    server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;

    ...

    // 默认 AOF 重写缓存区为 64M
    server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
  
    ...

    // 默认压缩链表中节点个数为 512 
    server.hash_max_ziplist_entries = OBJ_HASH_MAX_ZIPLIST_ENTRIES;
    // 默认压缩列表中存储的 value 大小为 64
    
    ...    

    // 重置 RDB save 的保存参数
    resetServerSaveParams();

    // 设置 rdb save 保存参数
    appendServerSaveParams(60 * 60, 1);  /* save after 1 hour and 1 change */
    appendServerSaveParams(300, 100);  /* save after 5 minutes and 100 changes */
    appendServerSaveParams(60, 10000); /* save after 1 minute and 10000 changes */

    ... 

    /* 
     * 命令表-我们可以在这里初始化它,因为它是初始配置的一部分,
     * todo: 命令名称可以通过 redis.conf 使用 rename-command 指令修改
     */
    server.commands = dictCreate(&commandTableDictType, NULL);
    server.orig_commands = dictCreate(&commandTableDictType, NULL);
    // 填充命令表格,根据协议填充
    populateCommandTable();
    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");

    // todo: slowlog 慢日志
    /* Slow log 和日志相关的配置 */
    // slow log 时间限制(纪录)默认值是 10000
    server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
    // showlog 最大长度,默认值是 128
    server.slowlog_max_len = CONFIG_DEFAULT_SLOWLOG_MAX_LEN;

    /* Latency monitor 延迟监控 */
    server.latency_monitor_threshold = CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD;

    /* Debugging */
    server.assert_failed = "<no assertion failed>";
    server.assert_file = "<no file>";
    server.assert_line = 0;
    server.bug_report_start = 0;
    server.watchdog_period = 0;
}

给上面参数设置完默认值之后,肯定就是加载 redis.conf 文件,获取我们设置的值。

loadServerConfig

void loadServerConfig(char *filename, char *options) {

     ...

    // 这里才是真正的加载 redis.conf 文件
    loadServerConfigFromString(config);
    sdsfree(config);
}

我们看到真正加载 redis.conf 实际是 loadServerConfigFromString 这个方法。

/**
 * 加载 redis.conf 配置文件
 *
 * @param config redis.conf 配置文件
 */
void loadServerConfigFromString(char *config) {
    
    ...   // 忽略局部变量定义的方法

    // 根据 \n 换行符切割字符串,存到 lines 数组里面 totlines: 表示多少行
    lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
    // 循环遍历每一行字符串
    for (i = 0; i < totlines; i++) {
        // sds 指针数组
        sds *argv;
        // argv 存储元素的个数
        int argc;

        linenum = i+1;
        // 移除 sds 字符串中的 \t\r\n 字符,方便后面解析参数
        lines[i] = sdstrim(lines[i]," \t\r\n");

        /*
         * todo:跳过配置文件中以 # 或者以空格开头的配置
         * 这里就会有个坑,有的人配置文件的时候不小心多了个空格,
         * 这样就会导致自己配置的内容不会生效
         *
         * 为什么起始字符不能有空格呢?
         * 因为后面解析数据的时候是根据空格解析的
         *
         * 注意:lines 只是一个指针数组,为什么可以 lines[i][0]
         * 因为 lines 里面存的都是sds 结构,然后 sds 就是一个字符数组
         * lines[i][0]: 表示第i个 sds 中下标为0的字符
         */
        if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
    }

    // 将 redis.conf 中我们自己配置的信息将默认值覆盖(忽略代码,很简单)
    ...
      
    // 释放 lines,totlines 的内存
    sdsfreesplitres(lines,totlines);
    return;

    // 打印出错误信息
loaderr:
    fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
    fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
    fprintf(stderr, ">>> '%s'\n", lines[i]);
    fprintf(stderr, "%s\n", err);
    exit(1);
}

上一篇下一篇

猜你喜欢

热点阅读