浅析Redis①:命令处理核心源码分析(上)

写在前面

Redis作为我们日常工作中最常使用的缓存数据库,其重要性不言而喻,作为普调开发者,我们在日常开发中使用Redis,主要聚焦于Redis的基层数据结构的命令使用,很少会有人对Redis的内部实现机制进行了解,对于我而言,也是如此,但一直以来,我对于Redis的内部实现都很好奇,它为什么会如此高效,本系列文章是旨在对Redis源代码分析拆解,通过阅读Redis源代码,了解Redis基础数据结构的实现机制。

关于Redis的源码分析,已经有非常多的大佬写过相关的内容,最为著名的是《Redis设计与实现》,对于Redis源码的分析已经非常出色,本系列文章对于源码拆解时,并不会那么详细,相信大部分读者应该不是从事Redis的二次开发工作,对于源码细节过于深入,会陷入细节的泥潭,这是我在阅读源码时尽量避免的,我尽量做到对大体的脉络进行梳理,讲清楚主干逻辑,细节部分,如果读者有兴趣,可以自行参阅源码或相关资料。

本系列源代码,基于Redis 3.2.6

前言

毫无疑问,Redis已经成为我们日常开发中最长使用的缓存数据库,Redis如此高效的原因,是因为采用了非阻塞I/O模型来处理命令请求,这是我们耳熟能详的事情了,那么Redis具体是如何实现非阻塞I/O的呢?Redis是如何接收命令请求,并执行命令,再返回给客户端的呢?我们来一起探究。

本篇是Redis源码分析系列的第一篇,我们来一起看一下Redis处理命令的核心实现机制。

Redis处理命令请求实现

我们可以思考一下,如果使用Java实现Redis,应该会怎么样?

我们需要编写main函数,在main中初始化Redis的配置,然后实现一些Servlet的接口,处理命令请求,然后使用一个NIO框架,处理请求命令,最后将结果返回给客户端。事实上,Redis的整体结构上,的确是这样实现的,首先第一步,Redis需要一个main函数,作为Redis的启动入口,在main中,需要做一系列的事情。

那由此为引,我们来看一下Redis的main函数实现。

阅读Redis的源码,一切的起点在server.c中,在该文件中,定义了main函数,作为整个工程的入口:

int main(int argc, char **argv) {
   
    struct timeval tv;
    int j;

// 省略,各种初始化操作检查
......
    
    // 核心1:初始化Server配置
    initServerConfig();
    // 从配置文件中加载配置信息
    loadServerConfig(configfile, options);

// 省略,各种初始化操作检查
......
	
    // 核心2:初始化Server
    // 重点如: 绑定监听端口号,设置 acceptTcpHandler 回调函数
    initServer();
    
// 省略,各种初始化操作检查
......
    // 从硬盘恢复数据,RBD/AOF
    loadDataFromDisk();
	
    // 核心3:设置核心函数beforeSleep,用于Redis进入事件驱动库的主循环之前被调用
    // 后面再讲
    aeSetBeforeSleepProc(server.el,beforeSleep);
    
    // 核心4:核中核,主函数循环,处理命令请求的核心函数
    aeMain(server.el);
    
    // 核心5:关闭服务,收尾工作
    aeDeleteEventLoop(server.el);
    return 0;
}

上面就是server.c中的main函数实现,这里我删除了很多非核心的检查方法,可以更清晰的聚焦mian函数的核心步骤,简单归纳一下main函数中都做了哪些事情:

1、Redis 会设置一些回调函数,当前时间,随机数的种子。回调函数实际上什么?举个例子,比如要给 Redis 发送一个关闭的命令,让它去做一些优雅的关闭,做一些扫尾清楚的工作,这个工作如果不设计回调函数,它其实什么都不会干。其实 C 语言的程序跑在操作系统之上,Linux 操作系统本身就是提供给我们事件机制的回调注册功能,所以它会设计这个回调函数,让你注册上,关闭的时候优雅的关闭,然后它在后面可以做一些业务逻辑。

2、不管任何软件,肯定有一份配置文件需要配置。首先在服务器端会把它默认的一份配置做一个初始化。

3、解析启动的参数。其实不管什么软件,它在初始化的过程当中,配置都是由两部分组成的。

第一部分,静态的配置文件;第二部分,动态启动的时候,main,就是参数给它的时候进去配置。

4、把服务端的东西拿过来,装载 Config 配置文件,loadServerConfig。

5、初始化服务器,initServer。

6、从磁盘装载数据。

7、有一个主循环程序开始干活,用来处理客户端的请求,并且把这个请求转到后端的业务逻辑,帮你完成命令执行,然后吐数据。

就这么一个过程。

OK,继续主题,我们希望了解是如何处理命令请求的,如果是Java语言,我们需要定义Servlet接口,并监听特定的端口,比如8080端口,以此来接收来自客户端的请求,但是对于C,是没有Servlet的,如果希望接收网络请求调用,需要通过socket进行网络通信,下面我们看一下Redis如何注册socket:

server.c initServer()

void initServer(void) {
   
// 省略,各种初始化操作检查
......
    
    // 核心1:创建epoll
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);

// 省略,各种初始化操作检查
......
    
    // 核心2:创建socket监听
    if (server.unixsocket != NULL) {
   
        unlink(server.unixsocket); /* don't care if this fails */
        server.sofd = anetUnixServer(server.neterr,server.unixsocket,
            server.unixsocketperm, server.tcp_backlog);
        if (server.sofd == ANET_ERR) {
   
            serverLog(LL_WARNING, "Opening Unix socket: %s", server.neterr);
            exit(1);
        }
        anetNonBlock(NULL,server.sofd);
    }

// 省略,各种初始化操作检查
......

    // 核心3:创建定时任务
    if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
   
        serverPanic("Can't create the serverCron time event.");
        exit(1);
    }

    // 核心4:重点,核中核,通过aeCreateFileEvent创建epoll监听socket,设置行为为READABLE,
    // 并注册回调函数,当socket接收到套接字时,会触发执行回调函数
    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.");
            }
    }
    if (server.sofd > 0 && aeCreateFileEvent(server.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值