笔者Redis事务使用相关文章链接:Redis 事务机制深入浅出、Redis WATCH事务监视机制与回滚。
前言
本篇文章将从源码角度分析整个Redis事务执行的流程,包括MULTI、EXEC、DISCARD、WATCH命令的源码实现以及相关数据结构。
本文源码版本为Redis 5.0,文中涉及到的源码均可在server.h、server.c以及multi.c三个文件中找到。
源码阅读不易,如出现纰漏或理解错误还望指正。
事务的执行流程
首先明确一个问题,在Redis中,服务器接受到任何命令后都不会立即执行,而是首先检查客户端内若干状态,这是事务实现的重要一环。
对于MULTI命令,实际上就是修改了客户端内的flags属性,该属性为int类型,专门用于标识状态,采用位存储(每一位的0和1对应某一状态的开启或关闭),MULTI指令执行后,会将客户端的状态修改为事务状态。
对于非事务相关命令,自然在执行前会先检查状态,若发现当前客户端处于事务关闭状态,则直接执行命令,若发现当前客户端出于事务开启状态,则会加入到事务相关的命令队列中等待执行。
对于EXEC命令,在执行命令前会先检查当前客户端是否有监视某数据库键,若没有或所监视键未被修改过,则按序从队列中取出储存的命令并依次执行,队列中全部命令执行完毕后修改事务状态,否则放弃当前事务执行。
对于DISCARD命令,放弃当前事务,实际上的操作是释放储存命令的队列,并修改事务状态。
对于WATCH命令,将参数键加入到一个字典中,随后在执行EXEC命令时会检查这些被监控的数据库键是否被修改过,被修改过则放弃执行事务。监视的周期为一个事务,即无论事务最终以哪种条件结束,监视都会随之失效。
源码详解
数据结构
首先先来察看客户端的数据结构定义,由于本文只关注事务执行流程,因此只保留相关属性,其余省略。
redisDb源码如下:
typedef struct redisDb {
// ...
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
// ...
} redisDb;
相关属性只有watched_keys,这是一个字典类型的属性,key为被监视的键,value为监视该键的客户端名。该属性在WATCH监视时用于判断当前客户端是否监视了某键。
客户端源码如下:
typedef struct client {
// ...
int flags; /* Client flags: CLIENT_* macros. */
multiState mstate; /* MULTI/EXEC state */
// 与当前客户端监视键相关联 见下文WATCH部分
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
// ...
} client;
flag属性
主要涉及到两个属性,首先介绍flags属性,正如前文提及,flags属性专门作为标识位,用于标识当前客户端的各种状态,具体的实现是,每一位对于一个特殊的状态,int型具有4字节32位,理论上可以存储32个只要两个状态的标识位,事实上源码显示Redis使用了其中的28位,对于flags的每一位,Redis都对他们进行特殊定义。
flags状态的宏定义如下,与client结构体相同,此处涉及到的状态非常多,此处只列举与事务相关的状态:
#define CLIENT_MULTI (1<<3) /* This client is in a MULTI context */
#define CLIENT_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */
#define CLIENT_DIRTY_EXEC (1<<12) /* EXEC will fail for errors while queueing */
其中可见flags中的低第4位用于标识当前客户端是否处于事务开启状态、低第6位用于标识WATCH监视的键是否有修改、第13位用于标识指令入队时是否存在错误,在后文的其他代码部分中还会见到这三个变量的身影。
multiState属性
multiState数据结构源码如下:
typedef struct multiState {
// 实际存储命令的数组
multiCmd *commands; /* Array of MULTI commands */
// 命令数组长度 用于记录当前事务有多少命令
int count; /* Total number of MULTI commands */
int cmd_flags; /* The accumulated command flags OR-ed together.
So if at least a command has a given flag, it
will be set in this field. */
int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState;
multiCmd数据结构如下:
typedef struct multiCmd {
// 命令参数
robj **argv;
// 参数数量
int argc;
// 命令指针 指向实际的命令
struct redisCommand *cmd;
} multiCmd;
MULTI执行流程
MULTI命令非常简单且容易理解,实际上只是修改了flags标识位而已,对应的函数如下:
void multiCommand(client *c) {
// 判断当前客户端是否已经出于事务开启状态 Redis不允许事务嵌套
if (c->flags & CLIENT_MULTI) {
addReplyError(c,"MULTI calls can not be nested");
return;
}
// 通过|= 或运算 将flags中的对应标识位修改
c->flags |= CLIENT_MULTI;
// 随后返回熟悉的OK信息提示
addReply(c,shared.ok);
}
事务开启后普通命令插入流程
在服务器端接收到客户端命令请求后,会经历四个阶段,分别是
- 命令读取
- 命令解析
- 命令执行
- 结果返回
指令检查事务是否开启是在命令执行阶段完成的,其对应函数processCommand,同样非关键部分省略,函数源码如下:
int processCommand(client *c) {
// ...
/* Exec the command */
// 判断分为两部分 首先判断当前客户端的事务状态
// 之后判断当前的命令是否为EXEC、DISCARD、MULTI或WATCH
// 除了四个事务相关命令外,其他的命令均会进入if分支入队
if (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
{
// 调用入队函数
queueMultiCommand(c);
// 入队后返回熟悉的提示信息QUEUED
addReply(c,shared.queued);
} else {
// ...
}
return C_OK;
}
随后是实现入队操作的函数queueMultiCommand,函数源码如下:
/* Add a new command into the M

本文深入分析Redis 5.0事务执行流程,包括MULTI, EXEC, DISCARD, WATCH命令的源码实现。讨论了如何检查事务状态、命令入队、监视键管理和事务执行条件。通过源码揭示了Redis客户端flags属性、multiState数据结构及其在事务中的作用。"
111794198,10324949,Qt连接数据库实战:QSqlDatabase与ODBC、MySQL,"['Qt开发', '数据库连接', 'MySQL', 'ODBC', '数据访问']
最低0.47元/天 解锁文章
751

被折叠的 条评论
为什么被折叠?



