12

你知道 Redis 服务器接收到一条命令是如何执行的吗?

 3 years ago
source link: http://www.justdojava.com/2021/05/30/redis/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

你知道 Redis 服务器接收到一条命令是如何执行的吗?

发表于 2021-05-30

| 分类于 Java

Hello 大家好,我是阿粉,Redis 作为工作中不可缺少的缓存组件,相信很多小伙伴都会使用到,我们日常使用的时候都是通过代码或者客户端去链接 Redis 服务器来操作数据的。那么一条简单的set name ziyou 命令是如何执行的,中间都经历了哪些过程想必很少会有人去了解。今天阿粉就带大家看一下一条简单的set name ziyou 命令是如何执行的。

1.png

我们可以看到在执行set name ziyou 这个命令过后,先显示一个OK 在终端里面。下面我们看下这整个过程都经历了哪些步骤。

命令的整个执行分为下面几个步骤,我们先看流程,在仔细分析:

  1. 客户端发送命令请求;
  2. 服务端读取命令请求;
  3. 命令执行器进行操作
    1. 命令执行器查找命令实现函数;
    2. 命令执行器执行预备操作;
    3. 命令执行器调用命令的实现函数;
    4. 命令执行器执行后续工作;
  4. 服务端将命令回复发送给客户端;
  5. 客户端接收并打印命令回复内容;

客户端发送命令请求

首先当客户端和服务端建立好了链接过后,当我们输入命令 set name ziyou 命令请求的时候,客户端会将这个命令进行协议转换,然后通过连接将转换后的协议发送到服务端。

比如当我们输入命令set name ziyou 的时候,客户端会将这个原始命令转换成*3\r\n$3\r\nset\r\n$4\r\nname\r\n$5\r\nziyou,这个协议大家应该比较眼熟,就是 Redis 管道的文件格式。简单解释下这个协议的意思,前面的*3 表示这个命令总共有三个参数,其中的$3,$4,$5 表示相应参数的长度。

服务端读取命令请求

当服务端收到该客户端的数据时,就会调用命令请求处理器来处理对应的消息。这块主要涉及到三个操作,第一个是保存命令,也就是会将命名的请求信息读取出来保存到对应客户端的输入缓冲区里面;保存完了过后会对输入缓冲区里面的内容进行解析,也就是对上面转换后的协议进行解析,解析出要执行的命令和对应的参数,将参数内容和参数个数保存到客户端的对应参数里面;第三步是调用命令执行器来执行命令。执行的命令和参数保存在RedisClient 结构的 argv 参数中,如下图所示,命令分析完成后,第三步才能更好的进行执行操作:

2.png

命令执行器

命令执行器查找实现函数

思考一个问题,我们这里 argv[0] 参数中的命令的是进行set 操作,在这里是个 set 字符串,那么 Redis 服务器是如何进行执行的呢?我们可以想到的是需要根据这个字符串找到对应的函数来进行操作,Redis 在内部有个的命令表,是一个字典结果,key 就是对应的命令名字,字典的值就是一个个 RedisCommand 结构,记录了命令的实现信息。

结构如下,简单来说就是通过 argv[0] 中的命令名称找到命令表中对应的redisCommand 结构,然后根据 proc 指针找到对应的执行命令。这里说明一下,命令名称的大小写没有任何影响,我们在输入的时候不用关心命令名称的大小写问题。

3.png

命令执行器执行预备操作

在 Redis 服务器执行相关命令之前,为了保证命令能够正确的执行,还需要进行相关的预备处理,部分预操作如下:

  1. 检查命令的参数和输入的参数个数是否一致,不一致则直接返回错误;
  2. 检查客户端是否通过身份验证,未通过身份验证则只能执行 AUTH 命令进行身份验证;
  3. 检查服务器的内容使用情况,为了保证命令执行成功,可能会需要进行内容回收;

除了上面的功能之外还有很多需要预备执行的动作,而且根据服务器部署的情况不一样,单机还是集群需要执行的操作还有不同。只有当所有的 预备操作都执行成功过后,才会真正的执行用户的命令。

由此可见 Redis 的性能是真正的高效,在有这么做操作流程的情况下还能保住命令执行的如此快速,不得不说真的很优秀。

命令执行器调用命令的实现函数

当前面的预备操作都完成过后,命令执行器就会调用对应的实现函数,在我们这里的例子就是调用 setCommand(redisClient *c) 函数进行数据写入操作,具体的 key 值和 value 值在 redisClient 结构中已经保存了,所以只要传递一个指针进去就可以了。setCommand() 命令执行后会返回一个OK\r\n ,这个返回会被保存到客户端的输出缓冲区当中,输出缓冲区的内容后续会被返回到客户端,给用户展示出来,如前面的图片显示的内容。

命令执行器执行后续工作

当命令执行器调用具体的实现函数过后,服务器还会有相应的一些操作要做,比如如果开启了慢日志功能,会检查是否要写入慢日志;如果开启了 AOF 则需要将刚刚执行的命令写入 AOF 的缓冲区中;以及如果有服务器备份或者监听的时候,会把刚刚执行的命令广播过去。

服务端将命令回复发送给客户端

实现函数执行完过后会将执行结果保存到客户端的输出缓冲区中,此时服务器的命令回复处理器会将缓冲区中的命令回复发送给客户端。命令回复处理器发送完数据过后会将客户端的输出缓冲区清理,方便后续的命令存入数据,同样回复的数据也是经过协议转换的。

客户端接收并打印命令回复内容

客户端收到回复数据过后就数据转换成可读的形式,输出到控制台。这样就得到了我们第一张图片的结果。

4.png

通过上面所有的过程,我们可以看到,就是一个简单的set name ziyou 这样的语句,整个执行的过程也还是很复杂的,Redis 服务器在设计的时候要考虑很多东西,安全,性能等等方面。

《Redis 设计与实现第二版》

Java Geek Tech wechat
欢迎订阅 Java 极客技术,这里分享关于 Java 的一切。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK