发布于 2015-09-17 14:51:52 | 206 次阅读 | 评论: 0 | 来源: 网络整理
前面的章节介绍了所有 Redis 的重要功能组件: 数据结构、数据类型、事务、Lua 环境、事件处理、数据库、持久化, 等等, 但是我们还没有对 Redis 服务器本身做任何介绍。
不过, 服务器本身并没有多少需要介绍的新东西, 因为服务器除了维持服务器状态之外, 最重要的就是将前面介绍过的各个功能模块组合起来, 而这些功能模块在前面的章节里已经介绍过了, 所以本章将焦点放在服务器的初始化过程, 以及服务器对命令的处理过程上。
本章首先介绍服务器的初始化操作, 观察一个 Redis 服务器从启动到可以接受客户端连接, 需要经过什么步骤。
然后介绍客户端是如何连接到服务器的, 而服务器又是如何维持多个客户端的不同状态的。
文章最后将介绍命令从发送到处理的整个过程, 并列举了一个 SET
命令的执行过程作为例子。
从启动 Redis 服务器, 到服务器可以接受外来客户端的网络连接这段时间, Redis 需要执行一系列初始化操作。
整个初始化过程可以分为以下六个步骤:
以下各个小节将介绍 Redis 服务器初始化的各个步骤。
redis.h/redisServer
结构记录了和服务器相关的所有数据, 这个结构主要包含以下信息:
Note
为了简洁起见,上面只列出了单机情况下的 Redis 服务器信息,不包含 SENTINEL 、 MONITOR 、 CLUSTER 等功能的信息。
在这一步, 程序创建一个 redisServer
结构的实例变量 server
用作服务器的全局状态, 并将 server
的各个属性初始化为默认值。
当 server
变量的初始化完成之后, 程序进入服务器初始化的下一步: 读入配置文件。
在初始化服务器的上一步中, 程序为 server
变量(也即是服务器状态)的各个属性设置了默认值, 但这些默认值有时候并不是最合适的:
等等。
为了让使用者按自己的要求配置服务器, Redis 允许用户在运行服务器时, 提供相应的配置文件(config file)或者显式的选项(option), Redis 在初始化完 server
变量之后, 会读入配置文件和选项, 然后根据这些配置来对 server
变量的属性值做相应的修改:
如果单纯执行 redis-server
命令,那么服务器以默认的配置来运行 Redis 。
另一方面, 如果给 Redis 服务器送入一个配置文件, 那么 Redis 将按配置文件的设置来更新服务器的状态。
比如说, 通过命令 redis-server /etc/my-redis.conf
, Redis 会根据 my-redis.conf
文件的内容来对服务器状态做相应的修改。
除此之外, 还可以显式地给服务器传入选项, 直接修改服务器配置。
举个例子, 通过命令 redis-server --port 10086
, 可以让 Redis 服务器端口变更为 10086
。
当然, 同时使用配置文件和显式选项也是可以的, 如果文件和选项有冲突的地方, 那么优先使用选项所指定的配置值。
举个例子, 如果运行命令 redis-server /etc/my-redis.conf --port 10086
, 并且 my-redis.conf
也指定了 port
选项, 那么服务器将优先使用 --port 10086
(实际上是选项指定的值覆盖了配置文件中的值)。
Redis 默认以 daemon 进程的方式运行。
当服务器初始化进行到这一步时, 程序将创建 daemon 进程来运行 Redis , 并创建相应的 pid 文件。
在这一步, 初始化程序完成两件事:
server
变量的数据结构子属性分配内存。为数据结构分配内存, 并初始化这些数据结构, 等同于对相应的功能进行初始化。
比如说, 当为订阅与发布所需的链表分配内存之后, 订阅与发布功能就处于就绪状态, 随时可以为 Redis 所用了。
在这一步, 程序完成的主要动作如下:
完成这一步之后, 服务器打印出 Redis 的 ASCII LOGO 、服务器版本等信息, 表示所有功能模块已经就绪, 可以等待被使用了:
_._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 2.9.7 (7a47887b/1) 32 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in stand alone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 6717 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-'
虽然所有功能已经就绪, 但这时服务器的数据库还是一片空白, 程序还需要将服务器上一次执行时记录的数据载入到当前服务器中, 服务器的初始化才算真正完成。
在这一步, 程序需要将持久化在 RDB 或者 AOF 文件里的数据, 载入到服务器进程里面。
如果服务器有启用 AOF 功能的话, 那么使用 AOF 文件来还原数据; 否则, 程序使用 RDB 文件来还原数据。
当执行完这一步时, 服务器打印出一段载入完成信息:
[6717] 22 Feb 11:59:14.830 * DB loaded from disk: 0.068 seconds
到了这一步, 服务器的初始化已经完成, 程序打开事件循环, 开始接受客户端连接。
以下是服务器在这一步打印的信息:
[6717] 22 Feb 11:59:14.830 * The server is now ready to accept connections on port 6379
以下是初始化完成之后, 服务器状态和各个模块之间的关系图:
当 Redis 服务器完成初始化之后, 它就准备好可以接受外来客户端的连接了。
当一个客户端通过套接字函数 connect
到服务器时, 服务器执行以下步骤:
accept
客户端连接,并返回一个套接字描述符 fd
。fd
创建一个对应的 redis.h/redisClient
结构实例,并将该实例加入到服务器的已连接客户端的链表中。fd
关联读文件事件。完成这三步之后,服务器就可以等待客户端发来命令请求了。
Redis 以多路复用的方式来处理多个客户端, 为了让多个客户端之间独立分开、不互相干扰, 服务器为每个已连接客户端维持一个 redisClient
结构, 从而单独保存该客户端的状态信息。
redisClient
结构主要包含以下信息:
Note
为了简洁起见,上面列出的客户端结构信息不包含复制(replication)的相关属性。
当客户端连上服务器之后, 客户端就可以向服务器发送命令请求了。
从客户端发送命令请求, 到命令被服务器处理、并将结果返回客户端, 整个过程有以下步骤:
redisClient
结构的查询缓存中。server
变量,并将命令的执行结果保存到客户端 redisClient
结构的回复缓存中,然后为该客户端的 fd
关联写事件。fd
的写事件就绪时,将回复缓存中的命令结果传回给客户端。至此,命令执行完毕。为了更直观地理解命令执行的整个过程, 我们用一个实际执行 SET 命令的例子来讲解命令执行的过程。
假设现在客户端 C1 是连接到服务器 S 的一个客户端, 当用户执行命令 SET YEAR 2013
时, 客户端调用写入函数, 将协议内容 *3\r\n$3\r\nSET\r\n$4\r\nYEAR\r\n$4\r\n2013\r\n"
写入连接到服务器的套接字中。
当 S 的文件事件处理器执行时, 它会察觉到 C1 所对应的读事件已经就绪, 于是它将协议文本读入, 并保存在查询缓存。
通过对查询缓存进行分析(parse), 服务器在命令表中查找 SET
字符串所对应的命令实现函数, 最终定位到 t_string.c/setCommand
函数, 另外, 两个命令参数 YEAR
和 2013
也会以字符串的形式保存在客户端结构中。
接着, 程序将客户端、要执行的命令、命令参数等送入命令执行器: 执行器调用 setCommand
函数, 将数据库中 YEAR
键的值修改为 2013
, 然后将命令的执行结果保存在客户端的回复缓存中, 并为客户端 fd
关联写事件, 用于将结果回写给客户端。
因为 YEAR
键的修改, 其他和数据库命名空间相关程序, 比如 AOF 、REPLICATION 还有事务安全性检查(是否修改了被 WATCH
监视的键?)也会被触发, 当这些后续程序也执行完毕之后, 命令执行器退出, 服务器其他程序(比如时间事件处理器)继续运行。
当 C1 对应的写事件就绪时, 程序就会将保存在客户端结构回复缓存中的数据回写给客户端, 当客户端接收到数据之后, 它就将结果打印出来, 显示给用户看。
以上就是 SET YEAR 2013
命令执行的整个过程。