【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 文件事件处理部分)

【专栏简介】

随着数据需求的迅猛增长,持久化和数据查询技术的重要性日益凸显。关系型数据库已不再是唯一选择,数据的处理方式正变得日益多样化。在众多新兴的解决方案与工具中,Redis凭借其独特的优势脱颖而出。

【技术大纲】

为何Redis备受瞩目?原因在于其学习曲线平缓,短时间内便能对Redis有初步了解。同时,Redis在处理特定问题时展现出卓越的通用性,专注于其擅长的领域。深入了解Redis后,您将能够明确哪些任务适合由Redis承担,哪些则不适宜。这一经验对开发人员来说是一笔宝贵的财富。

在这个专栏中,我们将专注于Redis的6.2版本进行深入分析和介绍。Redis 6.2不仅是我个人特别偏爱的一个版本,而且在实际应用中也被广泛认为是稳定性和性能表现都相当出色的版本

【专栏目标】

本专栏深入浅出地传授Redis的基础知识,旨在助力读者掌握其核心概念与技能。深入剖析了Redis的大多数功能以及全部多机功能的实现原理,详细展示了这些功能的核心数据结构和关键算法思想。读者将能够快速且有效地理解Redis的内部构造和运作机制,这些知识将助力读者更好地运用Redis,提升其使用效率。

将聚焦于Redis的五大数据结构,深入剖析各种数据建模方法,并分享关键的管理细节与调试技巧。

【目标人群】

Redis技术进阶之路专栏:目标人群与受众对象,对于希望深入了解Redis实现原理底层细节的人群

1. Redis爱好者与社区成员

Redis技术有浓厚兴趣,经常参与社区讨论,希望深入研究Redis内部机制、性能优化和扩展性的读者。

2. 后端开发和系统架构师

在日常工作中经常使用Redis作为数据存储和缓存工具,他们在项目中需要利用Redis进行数据存储、缓存、消息队列等操作时,此专栏将为他们提供有力的技术支撑。

3. 计算机专业的本科生及研究生

对于学习计算机科学、软件工程、数据分析等相关专业的在校学生,以及对Redis技术感兴趣的教育工作者,此专栏可以作为他们的学习资料和教学参考。

无论是初学者还是资深专家,无论是从业者还是学生,只要对Redis技术感兴趣并希望深入了解其原理和实践,都是此专栏的目标人群和受众对象

让我们携手踏上学习Redis的旅程,探索其无尽的可能性!


命令请求的执行过程

Redis服务器承担着多项关键职责,它要与众多客户端构建网络连接,对客户端传来的命令请求予以处理。在数据库层面,它负责存储客户端执行命令时所生成的数据,同时借助资源管理手段保障自身的稳定运转。

案例分析介绍

一个命令请求从发送到获得回复的过程中,客户端和服务器需要完成一系列操作。举个例子,如果我们使用客户端执行以下命令:

bash 复制代码
    redis> SET KEY VALUE
    OK

那么从客户端发送SET KEY VALUE命令到获得回复OK期间,客户端和服务器共需要执行以下操作:

  1. 客户端向服务器发送命令请求SET KEY VALUE
  2. 服务器接收并处理客户端发来的命令请求SET KEY VALUE,在数据库中进行设置操作,并产生命令回复OK
  3. 服务器将命令回复OK发送给客户端
  4. 客户端接收服务器返回的命令回复OK

发送命令请求

Redis服务器的命令请求来自Redis客户端,当用户在客户端中键人一个命令请求时,客户端会将这个命令请求转换成协议格式,然后通过连接到服务器的套接字,将协议格式的命令请求发送给服务器。

举个例子,假设用户在客户端键人了命令:

bash 复制代码
SET KEY VALUE

那么客户端会将这个命令转换成协议:

bash 复制代码
*3s3\r\nsET\r\ns3\r\nKEY\r\ns5\r\nVALUE\r\n

然后将这段协议内容发送给服务器

读取命令请求

当客户端与服务器之间的连接套接字因为客户端的写入而变得可读时,服务器将调用命令请求处理器来执行以下操作:

  1. 读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区里面。
  2. 对输人缓冲区中的命令请求进行分析,提取出命令请求。
    • 命令参数
    • 参数个数
    • 参数和参数个数保存到客户端状态的argv 属性和argc属性里面
  3. 调用命令执行器,执行客户端指定的命令

继续用上一个小节的SET命令为例子,下图展示了程序将命令请求保存到客户端状态的输人缓冲区之后,客户端状态的样子。 之后,分析程序将对输入缓冲区中的协议进行分析:*3\r\ns3\r\nsET\r\ns3\r\nKEY\r\ns5\r\nVALUE\r\n,并将得出的分析结果保存到客户端状态的argv 属性和argc属性里面。

客户端状态的argv属性和argc属性

服务器将通过调用命令执行器来完成执行命令所需的余下步骤,以下将分别介绍命令执行器所执行的工作。

命令执行器

命令执行器 要做的第一件事就是根据客户端状态的argv0参数,在命令表(command table )中查找参数所指定的命令,并将找到的命令保存到客户端状态的cmd 属性里面。

第一步:命令表-查找命令实现

命令表本质上是一个字典,该字典的键为具体的命令名称,例如 "set""get""del" 等;而字典所对应的值是redisCommand结构

每个redisCommand结构会记录一个 Redis 命令的实现相关信息,下面展示了这个结构各个主要属性的类型以及它们所起到的作用。

sflags属性的标识

sflags属性可以使用的标识值,以及这些标识的意义

  • w:这是一个写人命令,可能会修改数据库SET、RPUSH、DEL等等
  • r:这是一个只读命令,不会修改数据库GET、STRLEN、EXISTS等等
  • m:这个命令可能会占用大量内存,执行之前需要先检查服务器的内存使用情况,如果内存紧缺的话就禁止执行这个命令SET、APPEND、RPUSH、LPUSH、SADD、
  • a:这是一个管理命令SAVE、BGSAVE、SHUTDOWN等等
  • P:这是一个发布与订阅功能方面的命令:PUBLISH、SUBSCRIBE、PUBSUB等等
  • s:这个命令不可以在Lua脚本中使用BRPOP、BLPOP、BRPOPLPUSH、SPOP等等
  • R:这是一个随机命令,对于相同的数据集和相同的参数,命令返回的结果可能不同SPOP、SRANDMEMBER、SSCAN、RANDOMKEY等等
  • S:当在Lua脚本中使用这个命令时,对这个命令的输出结果进行一次排序,使得命令的结果有序,SINTER、SUNION、SDIFF、SMEMBERS、KEYS等等
  • l:这个命令可以在服务器载人数据的过程中使用:INFO、SHUTDOWN、PUBLISH等等。
  • t:这是一个允许从服务器在带有过期数据时使用的命令:SLAVEOF、PNG、WFO等等。
  • M:这个命令在监视器(monitor)模式下不会自动被传播(propagate),EXEC
案例分析

SET命令和GET命令作为例子,展示了redisCommand结构。

SET命令

SET命令的名字为"set",实现函数为setCommand

  • 个数(arity ):3命令接受三个或以上数量的参数
  • 标识(sflags ):"wm",表示SET命令是一个写人命令,并且在执行这个命令之前,服务器应该对占用内存状况进行检查,因为这个命令可能会占用大量内存。
GET命令

GET命令的名字为"get",实现函数为getCommand函数;

  • 个数(arity):2,表示命令只接受两个参数
  • 标识(sflags ):"r",表示这是一个只读命令
设置客户端状态的cmd指针

SET命令的例子,当程序以argv[0]作为输人,在命令表中进行查找时,命令表将返回"set"键所对应的redisCommand 结构,客户端状态的cmd指针会指向这个redisCommand结构。

第二步:命令表-执行预备操作

服务器已经将执行命令所需的命令实现函数: 但是在真正执行命令之前,程序还需要进行一些预备操作,从而确保命令可以正确、顺利地被执行,这些操作包括:

(尴尬,ProcessOn到期了,所以暂时就用了有水印的,后续会去除水印,哈哈)

  1. 检查客户端cmd指针是否为NULL,若是,表明命令未匹配实现,服务器终止后续操作并返回错误。
  2. 依据cmd指向的redisCommandarity属性,核查命令参数个数,不符则终止后续并返回错误,如arity为3,参数需≥3个。
  3. 检查客户端是否通过身份验证,未通过仅能执行AUTH命令,否则返回错误。
  4. 若开启maxmemory,执行命令前检查内存占用,必要时回收,失败则终止后续并返回错误。
  5. 客户端用SUBSCRIBEPSUBSCRIBE时,服务器仅执行SUBSCRIBEPSUBSCRIBEUNSUBSCRIBEPUNSUBSCRIBE,其他命令拒绝。
  6. 服务器数据载入时,客户端命令需带特定标识(如INFO等)才执行,否则拒绝。
  7. 服务器因Lua脚本超时阻塞,仅执行SHUTDOWN,nosaveSCRIPTKILL
  8. 客户端执行事务时,仅执行EXECDISCARDMULTIWATCH,其他入事务队列。
  9. 开启监视器功能,将命令及参数信息发送给监视器。完成预备操作后,服务器执行命令。
第三步:命令表-调用命令的实现函数

服务器已经将要执行命令 的实现保存到了客户端状态的cmd属性里面,并将命令的参数对象参数个数 分别保存到了客户端状态的argv属性和argc属性里面,当服务器决定要执行命令时,它只要执行以下语句就可以了:

bash 复制代码
// client是指向客户端状态的指针
client->cmd->proc(client);

因为,执行命令所需的实际参数都已经保存到客户端状态的argv属性里面了,所以命令的实现函数只需要一个指向客户端状态的指针作为参数即可,继续以之前的SET命令为例子,展示了客户端包含了命令实现、参数和参数个数的样子。

对于这个例子来说,执行语句:

bash 复制代码
client -> cmd -> proc(client) -> setCommand(client)

被调用的命令实现函数会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里面(buf属性和reply属性),之后实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端。

保存了命令回复的客户端状态

对于前面SET命令的例子来说,函数调用setCommand(client)将产生一个"+OK\r\n"回复,这个回复会被保存到客户端状态的buf属性里面。

第四步:执行后续工作

在执行完实现函数之后,服务器还需要执行一些后续工作:

  1. 若开启慢查询日志功能,慢查询日志模块检查是否为刚执行命令请求添加新日志。
  2. 依据刚执行命令耗时,更新redisCommand结构的milliseconds属性,calls计数器加1。
  3. 若开启AOF持久化功能,AOF持久化模块将刚执行命令请求写入AOF缓冲区。
  4. 若有从服务器复制当前服务器,将刚执行命令传播给所有从服务器。

当以上操作都执行完了之后,服务器对于当前命令的执行到此就告一段落了,之后服务器就可以继续从文件事件处理器中取出并处理下一个命令请求了。

第五步:将命令回复发送给客户端

当客户端的套接字变为可写状态时,命令回复处理器会将协议格式的命令回复"+OK\r\n"发送给客户端。

  1. 命令执行处理器 会将命令回复保存到客户端的输出缓冲区里面,并为客户端的套接字 关联命令回复处理器 ,当客户端套接字变为可写状态时,服务器就会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。
  2. 当命令回复发送完毕之后,回复处理器会清空客户端状态的输出缓冲区,为处理下一个命令请求做好准备。
第六步:客户端接收并打印命令回复

当客户端接收到协议格式的命令回复之后,它会将这些回复转换成人类可读的格式,并打印给用户查看 继续以之前的SET命令为例子,当客户端接到服务器发来的"+OK\r1n"协议回复时,它会将这个回复转换成"OK\n",然后打印给用户看:

bash 复制代码
redis>SET KEY VALUE
OK

以上就是Redis客户端和服务器执行命令请求的整个过程了。

相关推荐
小满zs3 小时前
Go语言第三章(五谷轮回)
后端·go
霸道流氓气质7 小时前
SpringBoot中通用工具类库(Utils)封装与使用实践
spring boot·后端·python
她说可以呀7 小时前
Redis哨兵
数据库·redis·bootstrap
心机之蛙qee9 小时前
Redis的主从、哨兵及集群
数据库·redis·缓存
MemoleCard10 小时前
深度解析Java当中的锁机制
后端
wear工程师10 小时前
ThreadLocal 在线程池里为什么会串数据:别只会答内存泄漏
java·后端
whyfail11 小时前
前端学 Spring Boot(2):一次点击,如何穿过整个后端?
前端·spring boot·后端
qq_2975746711 小时前
RuoYi框架二次开发系列(五)性能优化、Redis缓存实战、分布式部署、接口限流与安全加固生产方案
redis·缓存·性能优化
番茄炒鸡蛋加糖12 小时前
Redis--Lua 脚本原子性与滑动窗口限流
数据库·redis·lua