前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis执行用户命令的过程,居然是这样的!

Redis执行用户命令的过程,居然是这样的!

原创
作者头像
小许code
发布2023-08-10 10:56:23
3190
发布2023-08-10 10:56:23
举报
文章被收录于专栏:小许code小许code

前言

Redis想必每一个后端人员都是非常熟悉,在我日常的开发中,基本上使用Redis作为缓存中间件,而且使用Redis让我们完成很多需求、解决了不少业务问题,这里问个问题看看你会怎么答?

Redis怎么执行命令的呢?

面对这个问题想必大部分朋友心里的答案是:客户端发送命令给到服务端,服务端收到执行之后再处理将命令执行结果返回给客户端,简单来说如下图:

那么具体的更细节呢过程?

继续追问的话,能回答上来的还是比较少的,可能跟我之前一样无法表述出来,或者根本没深入了解学习过。

但是从今天开始我们讲能轻松应对,今天的文章将会深入Redis到底是如何执行我们输入的命令的,看了本文肯定能对你进阶学习Redis有很大的帮助!无论从面试角度,还是积累知识厚度方面都能有一个全面的认识。

有兴趣的同学可以看看之前关于MySQL的Select、Update执行过程的文章,做个知识积累。

分享文章内容前,老规矩,贴个文章大纲给大家,提前了解要讲的知识点。

📚 全文字数 : 5k+

⏳ 阅读时长 : 7min

📢 关键词 : Redis 、Socket、RESP协议、I/O多路复用

命令执行流程

在Redis中有两个重要的角色,一个是客户端Client,一个是服务器Server。而且它们是一对多的关系,也就是说Server会保存每个与之相连接的Client的状态信息。

我们一起看看一条Redis操作命令从客户端发送到服务器如何接受、处理并返回的具体实现。

启动建立连接

我们先看Redis服务端的启动,这也是客户端能建立连接的前提。

Redis服务器启动后,需要经过一些列的初始化及配置的设置,比如状态参数、用户配置、初始化数据结构等,主要包括下面这些。

Redis 客户端和服务器端是基于 socket 通信的,服务器端在初始化时会创建了一个 socket 监听,用于监听接客户端的 socket 连接

了解过源码的都知道,在 【redis.h/redisServer】 中redisServer结构体存储Redis服务器的所有信息,包括但不限于数据库、配置参数、命令表、监听端口与地址、客户端列表、若干统计信息、RDB与AOF持久化相关信息、主从复制相关信息、集群相关信息等。

而客户端连接服务器之前需要创建socket(一套固定的模式),然后根据设定的IP和端口号与服务器进行连接。

这里有个重要的知识点:在通过网络与redis服务器连接的普通客户端和lua脚本的客户端,服务器都会创建相对应的client 结构,用于记录他们的状态信息。

代码语言:javascript
复制
//客户端在redisServer结构中的属性
struct redisServer {
    ...
    // 存放普通客户端的列表
    list *clients;   /* List of active clients */
    // 存放lua脚本客户端
    client *lua_client;    /* The "fake client" to query Redis from Lua */
    ...
};

Redis客户端其实有三种类型:1:负责执行Lua脚本的伪客户端,2:用来加载aof文件的伪客户端,3:通过网络连接的普通客户端

到这里Redis客户端和服务端就完成连接,接下来继续看到底如何传输执行用户指令的!

客户端发送命令

当用户在客户端输入一条执行命令时,客户端会将这个命令请求转换成Redis相关的通信协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器。

Redis 通讯协议(RESP 协议,REdis Serialization Protocol)

比如我们输入 set xkey xiaoxu 命令会转换成如下格式:

看看转换后的格式注释,就知道各部分代表什么意思了

代码语言:javascript
复制
*3        //参数个数是*开头,3个参数
$3        //参数长度是$开头,命令长度
SET       //命令名称SET
$4        //参数长度是$开头,key长度
xkey      //key的内容
$6        //参数长度是$开头,value长度
xiaoxu      //value内容
参数个数是*开头,参数长度是$开头,每个参数通过\r\n隔开
回复协议格式:
* 状态回复(status reply)的第一个字节是 “+”,如:+ok\r\n
* 错误回复(error reply)的第一个字节是 “-“,如:-ERR unknown command xxx\r\n

服务端读取命令

通过连接套接字让客户端的写入而变得可读,服务端将读取协议内容,并存储到客户端的缓冲区,这里的缓冲区是client结构的输入缓冲区。

我们了解到每个连接到服务端的客户端,会保存在redisServer结构体中的clients链表中

代码语言:javascript
复制
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 被创建后,会分配两个缓冲区,输入 (发送)缓冲区和输出 (接收)缓冲区。在执行 send 之后,数据只是拷贝到了socket输入缓冲区,而什么时候向网络中输出,是由操作系统安排决定的。 一旦将数据写入到缓冲区,函数就可以成功返回,在识别到是 TCP协议后,再由 TCP 协议将数据从缓冲区一路发送到目标机器。 读取函数也是如此,它也是从输入缓冲区中读取数据,而不是直接从网络中读取。 注意:数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制

服务端处理执行命令

恭喜你,看到这里,你已经知道用户的操作命令令怎么传输和服务端怎么获取到指令了!继续往下看

查找命令

命令执行器根据客户端状态的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持久化记录、主从复制命令传播等。

回复消息给客户端

命令实现函数会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字关联命令回复处理器,当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。

发送完毕后,回复处理器会清空客户端状态的输出缓冲区,为下一个命令请求做好准备。

当客户端接收到协议格式的命令回复之后,它会将这些回复转换成人类可读的格式,并打印给用户观看。

比如之前的 set xkey xiaoxu,服务器发送的 "+OK\r\n",通过协议转换成 "OK\n"

代码语言:javascript
复制
redis > set xkey xiaoxu
OK

ok,到这里其实整个发送命令和接收回复的流程就完成了,大家可以通过下图,加深下印象!

事件和IO模型

事件机制

Redis服务器是一个事件驱动程序,它主要处理以下两种事件。

文件事件(file event):利用I/O复用机制,监听Socket等文件描述符上发生的事件,这类事件主要由客户端(或其他Redis服务器)发送网络请求触发,根据不同执行的任务来为套接字关联不同的事件处理器。

时间事件(time event):定时触发的事件,负责完成redis内部定时任务,如生成RDB文件、清除过期数据等

文件事件为不同的套接字关联了不同的处理器,组成部分分别是:套接字、I/O多路复用程序、文件事件分派器(dispatcher),文件事件处理器。

我们主要看下文件事件,它跟文章内容息息相关。

Redis 客户端与 Redis 服务端建立连接,发送命令,Redis 服务器响应命令都是需要通过事件机制。

AE_READABLE 事件:客户端与 Redis 服务器发起建立连接,监听套接字产生 AE_READABLE 事件。

AE_WRITEABLE 事件:响应客户端操作完成后,将产生 socket 的 AE_WRITEABLE 事件,命令回复处理器完成处理后,将解除 AE_WRITEABLE 事件与命令恢复处理器的关联。

I/O多路复用

I/O多路复用机制主要是为了减少线程切换带来的开销,同时也避免了 I/O 阻塞操作,从而大大提高了 Redis 的运行效率。

IO多路复用程序会同时监听多个socket,当被监听的socket准备好执行accept、read、write、close等操作时,与这些操作相对应的文件事件就会产生。

IO多路复用程序会把所有产生事件的socket压入一个队列中,然后有序地每次仅分配其中的一个socket发送给文件事件分派器

文件事件分派器接收到socket之后会根据socket产生的事件类型调用对应的事件处理器进行处理。

总结

我们知道了一条Redis命令请求从发送到完成的步骤,答题如下:

  1. Redis客户端发送命令请求到服务器
  2. 服务器读取命令请求,解析命令参数
  3. 命令执行器根据命令参数查找命令的实际实现函数,然后执行,接着回复执行结果给客户端

服务器的Server结构使用Clients链表来链接多个客户端的状态,包括我们的输入请求和输出结果、解析的命令参数等。

同样我们也了解了Socket简单知识

好了,今天关于Redis命令的执行过程就分享到这!希望大家喜欢

👨👩 你好,朋友,希望本文对你有帮助~🌐

欢迎点赞 👍、收藏 💙、关注 💡 三连支持一下~🎈

我是小许,下期见~🙇💻

参考:

《Redis设计与实现》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 命令执行流程
    • 启动建立连接
      • 客户端发送命令
        • 服务端读取命令
          • 服务端处理执行命令
            • 回复消息给客户端
            • 事件和IO模型
              • 事件机制
                • I/O多路复用
                • 总结
                相关产品与服务
                云数据库 Redis
                腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档