当 Redis 启动时 您所在的位置:网站首页 redisserver启动不了 当 Redis 启动时

当 Redis 启动时

2023-04-13 16:15| 来源: 网络整理| 查看: 265

Redis 作为现在最受欢迎的的键值数据库,在我们后端工程师的系统开发中,扮演着非常重要的角色。它超高的读写性能、丰富的存储结构、高可用集群设计以及活跃的社区,无疑是开源内存数据库的首选。我们今天就来了解一下 Redis 是如何处理一条命令的? 它在代码中又用了哪些小心思来提升性能?

数据来源 db-engines.com 在开始之前 在开始之前,确保我们已经对 Redis 的安装、运行、常用命令有了一定的了解,这样我们在尝试理解内部实现原理时才会有感性的认知。我们来复习一下吧:

通过 Docker 运行一个 Redis 容器并进入$ docker pull redis:4 $ docker exec -p 6379:6379 -itd --name redis redis:4 # 进入容器 $ docker exec -it redis sh

2.通过redis-cli连接,执行一些命令

$ redis-cli redis-cli(6379)> set name foo OK redis-cli(6379)> get name "foo"

我们看一下,在上面的过程中 Redis 到底发生什么?

从外部视角,即客户端的视角来看,可以分解为以下几步

Redis 服务启动起来客户端与 Redis 建立连接客户端发送set name foo命令,返回响应OK客户端发送get name命令,返回响应foo

那么接下来,我们从 Redis 内部视角(基于 Redis 5.0 的源码进行分析)出发,来看看,当一条命令set name foo被 Redis 接收到时,它到底“一个人抗下了多少”?

当 Redis 启动时

当 Redis 启动时,首先会对服务器进行初始化流程,包含以下的步骤:

初始化配置(系统默认配置)加载并解析配置文件(用户配置)初始化服务内部变量创建事件循环 eventLoop创建 socket 并开始监听创建文件事件与时间事件开始事件循环 初始化配置

第一步操作的核心函数为initServerConfig(void),有两百多行,主要是对服务器的一些核心参数设置系统默认配置,比如:

核心参数默认值 加载并解析配置文件

第二步操作的核心函数为void loadServerConfig(char *filename, char *options),参数中filename为配置文件的路径,options为命令行通过参数指定的配置信息,比如:

$ redis-server /config/redis/redis.conf -p 400

则/config/redis/redis.conf就会作为filename,-p 400作为options。 在解析配置文件时,会将整个配置文件内容加载到内存,通过\n进行分割,然后将对#开头的行进行跳过,它代表是注释。

初始化服务内部变量

第三步操作的核心函数是initServer(void),主要是初始化一些客户端链表、数据库、全局变量和共享变量等。 Redis 通过复用共享变量来减少频繁的内存分配,通过函数createSharedObjects(void)来实现,共享变量都被保存在全局结构体shared中,主要有:

用于响应的字符串,比如:shared.ok、shared.err0~10000的整数,比如:shared.integers[1] 创建事件循环 eventLoop

第四步操作的核心函数是aeCreateEventLoop(int setsiee),创建事件循环eventLoop,即分配结构体所需内存,并初始化结构体各字段,并调用aeApiCreate函数初始化了 epoll 对应的结构体。 创建 socket 并开始监听 第五步操作的核心函数是listenToPort(int port, int *fds, int *count) 这个函数中使用了server变量,这是一个全局变量

// server.c /* Global vars */ struct redisServer server; /* Server global state */

这个变量的初始化正提到的步骤三里操作的。server.bindaddr存储了用户在配置文件中写的所有IP地址。第五步所做的就是遍历用户配置的IP地址,建立非阻塞的 socket,进行监听。

创建文件事件与时间事件

接下来是第六步操作是创建文件事件与时间事件,Redis 把 socket 读写事件抽象为了文件事件,即aeFileEvent,通过事件循环进行执行。因此需要对刚才监听的 socket 创建对应的文件事件。 创建文件事件的处理核心函数是

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData)

我理解的是,利用 epoll 的非阻塞特性,为每个事件绑定一个对应的处理函数,当事件发生时,调用对应的处理函数进行处理即可。比如:监听事件的处理函数为acceptTcpHandler,实现了 socket 连接请求的 accept ,以及客户端对象的创建。 时间事件由定时任务函数触发,核心处理函数是aeCreateTimeEvent,时间事件实际上只有一个,他通过链表连接多个定时任务。

开始事件循环

最后一步是开始事件循环,核心处理函数代码很少,我们来看一下

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); } }

注意到eventLoop->beforesleep函数,在每次事件循环前执行,它会执行一些不是很费时的操作,如:集群相关操作、过期键删除操作(这里可称为快速过期键删除)、向客户端返回命令回复等。之后就是aeProcessEvents函数的执行,它会阻塞等待一小会儿文件事件的响应,如果有结果了,就去执行对应的处理函数,然后再去执行一下时间事件。 总结 本篇讲解了当 Redis 启动时程序做了哪些动作:初始化配置和变量、创建事件循环、监听 socket、创建文件事件和时间事件等等。也了解到 Redis 的全局变量和共享变量的作用,在共享变量中初始化0~10000的整数这种操作,和之前学习 Python 内存管理时它的小整数池是一个思路(空间换是时间)。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有