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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
全局流量管理 GTM,标准版 1个月
简介: Redis实战(10)-一条命令在Redis是如何执行的?

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实际执行命令。calll通过调用命令本身,即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线程机制,那这俩机制是在这个过程的什么阶段发挥作用?会不会影响命令执行原子性?

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
NoSQL 安全 测试技术
Redis游戏积分排行榜项目中通义灵码的应用实战
Redis游戏积分排行榜项目中通义灵码的应用实战
56 4
|
6天前
|
NoSQL 应用服务中间件 API
Redis是如何建立连接和处理命令的
本文主要讲述 Redis 是如何监听客户端发出的set、get等命令的。
|
2月前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
97 5
|
2月前
|
NoSQL Java Redis
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
这篇文章介绍了Redis的基本命令,并展示了如何使用Netty框架直接与Redis服务器进行通信,包括设置Netty客户端、编写处理程序以及初始化Channel的完整示例代码。
73 1
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
|
2月前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
145 2
|
1月前
|
存储 NoSQL Java
Redis命令:列表模糊删除详解
通过本文的介绍,我们详细探讨了如何在Redis中实现列表的模糊删除。虽然Redis没有直接提供模糊删除命令,但可以通过组合使用 `LRANGE`和 `LREM`命令,并在客户端代码中进行模糊匹配,来实现这一功能。希望本文能帮助你在实际应用中更有效地操作Redis列表。
61 0
|
2月前
|
缓存 NoSQL 测试技术
Redis如何解决频繁的命令往返造成的性能瓶颈!
Redis如何解决频繁的命令往返造成的性能瓶颈!
|
2月前
|
缓存 NoSQL Redis
Redis命令:列表模糊删除详解
Redis命令:列表模糊删除详解
90 3
|
2月前
|
NoSQL 关系型数据库 MySQL
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
本文全面阐述了Redis事务的特性、原理、具体命令操作,指出Redis事务具有原子性但不保证一致性、持久性和隔离性,并解释了Redis事务的适用场景和WATCH命令的乐观锁机制。
400 0
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
|
3月前
|
缓存 NoSQL 应用服务中间件
Redis实战篇
Redis实战篇