Redis执行流程大揭秘:从命令到结果的奇幻之旅
随着内卷这一情况的不断深化,在数据库方面只学习 MySQL 显然是不够的。那么效率极高的 Redis 就成为了学习首选,以至于现在熟练掌握 Redis 已经成为应届生的必备技能了。
笔者也是在一个多月前第一次接触到 Redis,期间断断续续地学了一点内容,但并不是深入系统的学习,只是去看了一下几个常见问题的八股文,太浮躁了。既然要直接准备秋招,那就还有大把的时间,所以今天开始系统地学习一下 Redis 相关的内容。
安装、运行这些最开始的东西就不多赘述了,毕竟操作 Redis 的代码也已经写了不少了。那除了这些以外,还有什么适合当作学习的第一步呢?
今天的文章将会深入 Redis 到底是如何执行我们输入的命令的。
Redis 是怎么执行命令的?
面对这个问题,大部分朋友心中的答案是:客户端发送命令给到服务端,服务端收到执行之后再处理将命令执行结果返回给客户端,简单来说如下图:
显然这样的答案不能帮我们了解 Redis 的工作原理,还是看看更细节的过程吧。
命令执行流程
一条命令的执行过程有很多细节,但大体可分为:客户端先将用户输入的命令,转化为 Redis 相关的通讯协议,再用 socket 连接的方式将内容发送给服务器端,服务器端在接收到相关内容之后,先将内容转化为具体的执行命令,再判断用户授权信息和其他相关信息,当验证通过之后会执行最终命令,命令执行完之后,会进行相关的信息记录和数据统计,然后再把执行结果发送给客户端,这样一条命令的执行流程就结束了。如果是集群模式的话,主节点还会将命令同步至子节点,下面我们一起来看更加具体的执行流程。
第一步:连接
一切的开始,都是从客户端和Redis服务器之间的连接开始的。这就好比你和朋友约好了一起去探险,首先得打个电话确认一下。
- 客户端发送连接请求:客户端就像那个迫不及待的探险者,发出了一条连接请求。
- Redis服务器接受请求:Redis服务器则像是守门员,确认探险者的身份后,挥舞着双手迎接他们。
在连接之前,我们先看一下 Redis 服务器的启动,这也是客户端能建立连接的前提。
Redis服务器启动后,需要经过一些列的初始化及配置的设置,比如状态参数、用户配置、初始化数据结构等,流程如下:
Redis 客户端和服务器端是基于 socket 通信的,服务器端在初始化时会创建了一个 socket 监听,用于监听接客户端的 socket 连接。
1 | void initServer(void) { |
在 【redis.h/redisServer】 中redisServer结构体存储Redis服务器的所有信息,包括但不限于数据库、
配置参数、命令表、监听端口与地址、客户端列表、若干统计信息、RDB与AOF持久化相关信息、主从复制相关信息、集群相关信息等。
而客户端连接服务器之前需要创建socket(一套固定的模式),然后根据设定的IP和端口号与服务器进行连接。
这里有个重要的知识点:在通过网络与redis服务器连接的普通客户端和lua脚本的客户端,服务器都会创建相对应的client 结构,用于记录他们的状态信息。
1 | //客户端在redisServer结构中的属性 |
Redis客户端其实有三种类型:1:负责执行Lua脚本的伪客户端,2:用来加载aof文件的伪客户端,3:通过网络连接的普通客户端
到这里Redis客户端和服务端就完成连接,接下来继续看到底如何传输执行用户指令的!
第二步:发送与解析
当连接建立后,客户端会发送命令给Redis服务器。这时候,就好像你给你的探险伙伴发出了一条魔法指令。
命令发送:你输入的命令就像是一封魔法信件,通过网络这个传送门,飞向Redis服务器。
命令解析:Redis服务器收到信件后,会认真阅读(解析)你写的内容,确保每个字都理解无误。
客户端发送命令
当 socket 成功连接之后,客户端会先把命令转换成 Redis 通讯协议(RESP 协议,REdis Serialization Protocol)发送给服务器端,这个通信协议是为了保障服务器能最快速的理解命令的含义而制定的,如果没有这个通讯协议,那么 Redis 服务器端要遍历所有的空格以确认此条命令的含义,这样会加大服务器的运算量,而直接发送通讯协议,相当于把服务器端的解析工作交给了每一个客户端,这样会很大程度的提高 Redis 的运行速度。
比如我们输入 set xkey xiaoxu
命令会转换成如下格式:
你问上面的是什么意思,来看下面的解释:
1 | *3 //参数个数是*开头,3个参数 |
服务端读取命令
通过连接套接字让客户端的写入变得可读,服务端将读取协议内容,并存储到客户端的缓冲区,这里的缓冲区是client结构的输入缓冲区。
每个连接到服务端的客户端,会保存在redisServer结构体中的clients链表中
1
2
3
4
5
6
7
8
9
10
11 typedef struct client {
...
// 客户端状态的输入缓冲区,保存客户端的命令请求
sds querybuf; /* Buffer we use to accumulate client queries. */
// 下面这两个是解析出来的命令和参数
int argc; /* Num of arguments of current command. */
robj **argv; /* Arguments of current command. */
// 一个是根据argv[0]解析出来的命令,一个是最后一次执行的命令
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
...
}
接着会对输入缓冲区中的命令请求进行分析,提取解析出命令请求中包含的命令参数,以及命令参数的个数,然后分别将参数和参数个数保存到客户端状态的argv属性和argc属性里面。
这里插播一个小知识!
socket 小知识:每个 socket 被创建后,会分配两个缓冲区,输入缓冲区和输出缓冲区。 写入函数并不会立即向网络中传输数据,而是先将数据写入缓冲区中,再由 TCP 协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是 TCP 协议负责的事情。 注意:数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。 读取函数也是如此,它也是从输入缓冲区中读取数据,而不是直接从网络中读取。
第三步:命令执行
解析完命令之后,Redis服务器开始执行命令。这一过程就像是启动了一个巨大的魔法阵,能量开始在其中流动。
- 查找命令:Redis服务器会在它的魔法书(命令表)中找到相应的魔法(命令)来施展。
- 执行命令:服务器根据魔法书中的指示,精准地执行命令。
查找命令
命令执行器根据客户端状态的argv[0]
参数,在命令表中查找参数所指定的命令,本文中的argv[0]
参数就是 set ,找到命令后保存到客户端状态的 cmd 属性。
命令表:其实就是一个字典,字典的键是命令名称,比如”set”、”get”、”del”;而值则是一个个redisCommand结构,每个redisCommand结构记录了一个Redis命令的实现信息。
SET命令的名字为”set”,实现函数为setCommand;命令的参数个数为-3,表示命令接受三个或以上数量的参数;命令的标识为”wm”,表示SET命令是一个写入命令。
GET命令的名字为 “get”,实现函数为getCommand函数;命令的参数个数为2,表示命令只接受两个参数;命令的标识为”r”,表示这是一个只读命令。
执行准备:参数、权限、内存校验
获得了执行需要的命令、参数后,服务器还需要做一些校验:
命令校验:检查客户端状态的cmd指针是否指向NULL。
参数校验:根据客户端cmd属性指向的redisCommand结构的arity属性,检查命令请求所给定的参数个数是否正确。
权限校验:检查客户端是否已经通过了身份验证,未通过身份验证的客户端只能执行AUTH命令。
内存检测:如果服务器打开了maxmemory功能,那么在执行命令之前,先检查服务器的内存占用情况,并在有需要时进行内存回收,从而使得接下来的命令可以顺利执行
其他校验..
执行命令
服务器将要执行命令的实现保存到了客户端状态的cmd属性里面,并将命令的参数和参数个数分别保存到了客户端状态的argv属性和argv属性里面,当服务器执行命令时,只需要一个指向客户端状态的指针作为参数,调用实际执行函数。
被调用的命令实现函数会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里面(buf属性和reply属性),之后实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端
后续:命令、参数、AOF
执行完毕后,会有一些后续操作,包括慢日志记录、redisCommand结构属性更新、AOF持久化记录、主从复制命令传播等。
第四步:返回结果
命令执行完毕后,Redis服务器会将结果通过网络传送回客户端。这就像是魔法信使带着宝贵的探险成果返回你的身边。
- 结果生成:执行完命令后,Redis服务器生成结果,就像是从宝箱中拿出了宝物。
- 结果传送:通过网络传送门,结果迅速飞回客户端。
- 客户端接收:最终,客户端收到结果,探险圆满成功!
命令实现函数会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器,当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。
发送完毕后,回复处理器会清空客户端状态的输出缓冲区,为下一个命令请求做好准备。
当客户端接收到协议格式的命令回复之后,它会将这些回复转换成人类可读的格式,并打印给用户观看。
比如之前的 set xkey xiaoxu,服务器发送的 “+OK\r\n”,通过协议转换成 “OK\n”
ok,到这里其实整个发送命令和接收回复的流程就完成了。
总结
我们知道了一条Redis命令请求从发送到完成的步骤,答题如下:
- Redis客户端发送命令请求到服务器
- 服务器读取命令请求,解析命令参数
- 命令执行器根据命令参数查找命令的实际实现函数,然后执行,接着回复执行结果给客户端
服务器的Server结构使用Clients链表来链接多个客户端的状态,包括我们的输入请求和输出结果、解析的命令参数等。
进入到闲聊部分,最近两天好像并没有什么有意思的事情发生,再决定不找实习之后反倒有几家公司找我要简历,当然还是老样子,要过简历之后就开始已读不回了,有时候想想当一个 HR 好爽啊,可以随时耍求职的人玩。好在我的心态已经放平了。
你问这么抽象的标题是怎么想出来的。对,就是ChatGPT给的,我觉得很好,优点标题党的意思了,营销号要失业了。
为了秋招顺利,我还是觉得要学一下 Java 的东西,只凭 Go 还是优点不安心。先把前段时间积压的东西学完,然后学着做一个项目。
对了,这一段时间的网络奇差无比,这样不好。月初有钱了,又想要冲动消费了,今天去看了苹果的头戴耳机,有点心动了,怎么办,克制一下吧。