Redis实战(10)-一条命令在Redis是如何执行的?

2023-09-21 16:39:27

Redis Server一旦和某客户端建立连接,就会在事件驱动框架中注册可读事件,对应客户端的命令请求。

整个命令处理过程可分阶段:

  • 命令解析,processInputBufferAndReplicate
  • 命令执行,processCommand
  • 结果返回,addReply

1 命令读取:readQueryFromClient

会从客户端连接的socket中,读取最大为readlen长度的数据,readlen大小为宏定义PROTO_IOBUF_LEN,默认16KB。

接着根据读取数据的情况,进行异常处理,如:

  • 数据读取失败

  • 或客户端连接关闭等

若当前客户端是主从复制中的主节点,readQueryFromClient会把读取的数据,追加到用于主从节点命令同步的缓冲区中。

最后,调用processInputBuffer,进入命令解析阶段。

void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
   ...
   readlen = PROTO_IOBUF_LEN;  // 从客户端socket中读取的数据长度,默认16KB
   ...
   c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);  // 给缓冲区分配空间
   nread = read(fd, c->querybuf+qblen, readlen);  // 调用read从描述符为fd的客户端socket中读取数据
    ...
    processInputBufferAndReplicate(c);  // 进一步处理读取内容
}

2 命令解析:processInputBuffer

根据当前客户端是否有CLIENT_MASTER标记,执行如下分支:

  • Case1

    客户端无CLIENT_MASTER标记,即当前客户端不属于主从的Master。processInputBufferAndReplicate直接调processInputBuffer,对客户端输入缓冲区中的命令和参数进行解析。所以在这里,实际执行命令解析的函数是processInputBuffer

  • Case2

    客户端有CLIENT_MASTER标记。processInputBufferAndReplicate除了会调用processInputBuffer,解析客户端命令,还会调用replicationFeedSlavesFromMasterStream,将主节点接收到的命令同步给从节点

最终命令解析就在processInputBuffer

  • 首先,processInputBuffer函数会执行一个while循环,不断从客户端的输入缓冲区读数据

  • 然后,判断读取到的命令格式,是否以“*”开头:

    • 命令 *开头,processInputBuffer会调processMultibulkBuffer解析读取到的命令

    • 不是*开头,即管道命令,命令和命令间用换行符\r\n分隔的。如使用Telnet发给Redis的命令就属该类型命令。processInputBuffer会调用processInlineBuffer解析命令。

命令解析完成后,processInputBuffer就会调用processCommand,进入命令处理的第三阶段:命令执行。

执行流程图

3 命令执行:processCommand

实际执行命令前的主要逻辑:

  1. processCommand调moduleCallCommandFilters,将Redis命令替换成module想替换的命令

  2. processCommand判断当前命令是否为quit命令并做相应处理

  3. processCommand调lookupCommand,在全局变量server的commands成员变量中查找相关命令

全局变量server的commands成员变量是个哈希表,定义在redisServer结构体:

commands成员变量的初始化是在initServerConfig,调用dictCreate完成哈希表创建,再调用populateCommandTable将Redis提供的命令名称和对应的实现函数,插入哈希表。

而这其中的populateCommandTable使用redisCommand结构体数组redisCommandTable。

redisCommandTable数组在server.c定义,它的每一个元素是redisCommand结构体类型的记录,对应Redis实现的一条命令。即redisCommand结构体记录当前命令所对应的实现函数。

如下代码展示GET、SET等命令信息,实现函数getCommand,setCommand:

所以lookupCommand会根据解析的命令名称,在commands对应的哈希表中查找相应命令。

查到对应命令后,processCommand就会检查,如命令参数是否有效、发送命令的用户是否进行过验证、当前内存的使用情况等。

等processCommand对命令做完各种检查,就开始执行命令,判断当前客户端是否有CLIENT_MULTI标记:

  • 有,说明要处理Redis事务相关命令

    按事务要求,调queueMultiCommand:将命令入队保存,等待后续再一把梭

  • 无,无关事务特性

    调call实际执行命令。call通过调用命令本身,即redisCommand结构体中定义的函数指针完成。每个redisCommand结构体中都定义了其对应实现函数,在redisCommandTable数组。

分布式锁的加锁操作就是使用SET命令,就通过SET命令看一个命令实际执行过程。

SET命令对应实现函数setCommand:

  • 首先会判断命令参数,如是否带有NX、EX、XX、PX等可选项,若有,就会记录这些标记
  • 然后,调用setGenericCommand:根据setCommand记录的命令参数标记,进行相应处理。如命令参数中有NX,则setGenericCommand会调用lookupKeyWrite,查找要执行SET命令的K是否已存在
  • 若K已存在,则setGenericCommand会调用addReply,返回NULL,正符合分布式锁语义。

若SET命令可正常执行,即:

  • 命令带NX选项,但K不存在

  • 或带有XX选项,但K已存在

这样setGenericCommand就会调用setKey完成KV对的实际插入:

setKey(c->db,key,val);

然后,若命令设置了TTL,setGenericCommand还会调用setExpire函数设置过期时间。最后,setGenericCommand调用addReply函数,将结果返给客户端:

addReply(c, ok_reply ? ok_reply : shared.ok);

SET命令执行流程图

无论:

  • 在命令执行过程中,发现不符合命令的执行条件
  • 或是命令能成功执行

addReply函数都会被调用以返回结果。所以,这就进入命令处理过程的最后一个阶段:结果返回阶段。

4 结果返回:addReply

调用prepareClientToWrite,并在prepareClientToWrite中调用clientInstallWriteHandler,将待写回客户端加入到全局变量server的clients_pending_write列表。

然后,addReply会调用_addReplyToBuffer等函数,将要返回的结果添加到客户端的输出缓冲区。

至此,这就是一条命令如何从读取,经过解析、执行等步骤,最终将结果返给客户端,该过程以及涉及的主要函数:

若在前面命令处理过程中,都由I/O主线程处理,则命令执行的原子性肯定能得到保证,分布式锁的原子性也相应得到保证。

FAQ

但若这个处理过程配合I/O多路复用机制和多IO线程机制,那这俩机制是在这个过程的什么阶段发挥作用?会不会影响命令执行原子性?

更多推荐

动态规划 Ⅱ

62.不同路径一个机器人位于一个mxn网格的左上角(起始点在下图中标记为“Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?思路:dp[i][j]=dp[i-1]dp[j]+dp[i]+dp[j-1],dp[i][j]=0when

数据结构与算法(C语言版)P1---算法效率

算法的效率:算法的时间复杂度和空间复杂度【本节目标】1.算法效率2.时间复杂度3.空间复杂度4.常见时间复杂度以及复杂oj练习1、算法效率1.1、如何衡量一个算法是的好坏如何衡量一个算法的好坏呢?比如斐波那契数列:longlongFib(intN){if(N<3)return1;returnFib(N-1)+Fib(N

MFC网络编程2——异步套接字

从上一节(MFC网络编程1——网络基础及套接字)中,我们了解了网络的部分基础知识以及套接字的使用,这一节,我们学习异步套接字的使用。Windows套接字在两种模式下执行I/O操作,阻塞模式和非阻塞模式。在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回,例如,程序中调用了recv

分布式事务

1.分布式事务问题1.1.本地事务本地事务,也就是传统的单机事务。在传统数据库事务中,必须要满足四个原则:1.2.分布式事务分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如:跨数据源的分布式事务跨服务的分布式事务综合情况在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成

上海亚商投顾:沪指失守3100点补缺口 华为概念股逆市活跃

上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。一.市场情绪三大指数昨日继续调整,沪指失守3100点,深成指跌破万点大关,创业板指续创3年多以来新低。华为概念股逆市活跃,捷荣技术再度涨停,18个交易日累计涨超250%,光弘科技、荣联科技、天邑股份、常山北明等多股封板。

UOS QTextEdit设置换行和滚动条(bug自动换行时右侧个别字符被遮盖)

一、环境UOS_x86/QT5/C++二、qtextEdit换行设置下图在ui界面lineWrapMode这个参数可以设置换行相关:NoWrap是不换行、WidgetWidth是自动换行(按textEdit的宽度换行)、下面两个是可以自定义每行的宽度,如果选了这两个,就可通过下面LineWrapColumnOrWidt

运维面试宝典

【Linux基础篇】1.描述Linux运行级别0-6的各自含义0:关机模式1:单用户模式<==破解root密码2:无网络支持的多用户模式3:有网络支持的多用户模式(文本模式,工作中最常用的模式)4:保留,未使用5:有网络支持的X‐windows支持多用户模式(桌面)6:重新引导系统,即重启2.描述Linux系统从开机到

【无标题】

一、Vue脚手架搭建(一)安装与配置1.npmconfigsetregistryhttp://registry.npm.taobao.org/npminstall-g@vue/clivue--version//进入目录小白做毕设2024vuecreatevue2.输入cdvuenpmrunserve3.成功(二)结构解

代码变更风险可视化系统建设与实践

总第575篇2023年第027篇本文整理自美团技术沙龙第77期《美团亿级流量系统的质量风险防控和稳定性治理实践》。文章第一部分介绍了软件系统风险与变更;第二部分介绍了代码变更风险可视化系统的能力建设;第三部分介绍了整个系统在美团内部实践落地的情况;最后是对未来的规划和展望。希望对大家能有所帮助或启发。1软件系统风险与变

SMTP是什么?谈谈SMTP的含义

SMTP,即SimpleMailTransferProtocol,也称为简单邮件传输协议,是一种用于电子邮件传输的协议。它能够将邮件从发送者的电子邮件客户端传输到接收者的电子邮件服务器,并通过其他协议将邮件传递给接收者的电子邮件客户端。SMTP协议的作用是让邮件能够成功投递并发送到指定的收件人邮箱。蜂邮给大家说说:SM

【产品运营】如何提升B端产品的竞争力(上)

B端产品的核心竞争力不是只有产品功能丰富度、易用度这些维度,判断产品核心竞争力应该基于产品所定位解决的问题场景。B端产品的成交因素很多,包括产品本身、公司品牌、客情关系、成功案例、产品定价、客户成熟度、需求匹配度等,本文只谈产品本身。一、产品竞争力类型B端产品本身的核心竞争力不仅只有产品功能丰富度、易用度这些维度,判断

热文推荐