深入理解Redis 事务机制及执行流程源码解析

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

笔者Redis事务使用相关文章链接:Redis 事务机制深入浅出Redis WATCH事务监视机制与回滚

前言

本篇文章将从源码角度分析整个Redis事务执行的流程,包括MULTIEXECDISCARDWATCH命令的源码实现以及相关数据结构。

本文源码版本为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);
}

事务开启后普通命令插入流程

在服务器端接收到客户端命令请求后,会经历四个阶段,分别是

  1. 命令读取
  2. 命令解析
  3. 命令执行
  4. 结果返回

指令检查事务是否开启是在命令执行阶段完成的,其对应函数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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7rulyL1ar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值