redis源码分析之事务Transaction(下)
接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令。
原文地址:http://www.jianshu.com/p/e22615586595
看本篇文章前需要先对上面文章有所了解:
redis源码分析之事务Transaction(上)
一、redis事务核心命令简介
redis事务操作核心命令:
//用于开启事务
{"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0},
//用来执行事务中的命令
{"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0},
//用来取消事务
{"discard",discardCommand,1,"sF",0,NULL,0,0,0,0,0},
在redis中,事务并不具有ACID的概念,换句话说,redis中的事务仅仅是保证一系列的命令按顺序一个一个执行,如果中间失败了,并不会进行回滚操作。
使用redis事务举例如下:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> set c c
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
127.0.0.1:6379>
二、redis事务核心命令源码分析
关于事务的几个命令所对应的函数都放在multi.c文件中。
首先来看一下multi命令,该命令用于标记客户端开启事务状态,因此它做的就是修改客户端状态,代码很简单,如下:
void multiCommand(client *c) {
//如果客户端已经是事务模式,则返回错误提示信息
if (c->flags & CLIENT_MULTI) {
addReplyError(c,"MULTI calls can not be nested");
return;
}
//设置客户端为事务模式
c->flags |= CLIENT_MULTI;
//返回结果
addReply(c,shared.ok);
}
接下来看下redis处理命令逻辑中的一段源码:
这段代码在server.c文件中的processCommand方法中:
//如果客户端处于事务状态且当前执行的命令不是exec,discard,multi跟watch命令中的一个
//则把当前命令加入一个队列
if (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
{
//加入队列
queueMultiCommand(c);
//返回结果
addReply(c,shared.queued);
} else {
//执行当前命令
call(c,CMD_CALL_FULL);
c->woff = server.master_repl_offset;
if (listLength(server.ready_keys))
handleClientsBlockedOnLists();
}
看入队操作源码前,先来熟悉几个数据结构,redis会把每个连接的客户端封装成一个client对象,该对象中含有大量字段用来保存需要的信息,发布订阅功能也使用对应的字段进行存储,事务当然也不例外,如下:
//每个客户端对象中有一个mstate字段用来保存事务上下文
typedef struct client {
multiState mstate;
}
//事务包装类型
typedef struct multiState {
//当前事务中需要执行的命令数组
multiCmd *commands;
//需要执行的命令数量
int count;
//需要同步复制的最小数量
int minreplicas;
//同步复制超时时间
time_t minreplicas_timeout;
} multiState;
//事务中执行命令的封装类型
typedef struct multiCmd {
//参数
robj **argv;
//参数数量
int argc;
//命令本身
struct redisCommand *cmd;
} multiCmd;
了解了基本的数据结构以后,再来看下入队操作:
void queueMultiCommand(client *c) {
//类型前面有说明
multiCmd *mc;
int j;
//扩容,每次扩容一个命令的大小
c->mstate.commands = zrealloc(c->mstate.commands,
sizeof(multiCmd)*(c->mstate.count+1));
//c++中给数组最后一个元素赋值语法实在是有点难懂...
mc = c->mstate.commands+c->mstate.count;
//初始化mc各个字段
mc->cmd = c->cmd;
mc->argc = c->argc;
mc->argv = zmalloc(sizeof(robj*)*c->argc);
//把参数一个一个拷贝过来
memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);
for (j = 0; j < c->argc; j++)
incrRefCount(mc->argv[j]);
c->mstate.count++;
}
上面是把命令加入事务命令数组的中的逻辑,由于在执行事务过程中也会执行删除事务的操作,因此在看执行事务逻辑之前我们先看下删除事务的实现原理。
当事务执行完成,执行错误或者客户端想取消当前事务,都会跟discard命令有联系,一起看下源码:
void discardCommand(client *c) {
//如果当前客户端没有处于事务状态,则返回错误信息
if (!(c->flags & CLIENT_MULTI)) {
addReplyError(c,"DISCARD without MULTI");
return;
}
//删除事务
discardTransaction(c);
//返回结果
addReply(c,shared.ok);
}
//具体的删除逻辑
void discardTransaction(client *c) {
//释放客户端事务资源
freeClientMultiState(c);
//初始化客户端事务资源
initClientMultiState(c);
//状态位还原
c->flags &= ~(CLIENT_MULTI|CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC);
//取消已watch的key,该函数上面文章中已经进行过分析,不赘述
unwatchAllKeys(c);
}
//释放事务队列中的每个命令
void freeClientMultiState(client *c) {
int j;
for (j = 0; j < c->mstate.count; j++) {
int i;
multiCmd *mc = c->mstate.commands+j;
//挨个释放命令的参数
for (i = 0; i < mc->argc; i++)
decrRefCount(mc->argv[i]);
zfree(mc->argv);
}
//最后释放命令本身
zfree(c->mstate.commands);
}
//事务相关字段设为初始值
void initClientMultiState(client *c) {
c->mstate.commands = NULL;
c->mstate.count = 0;
}
到这里,我们已经了解了开启事务模式,把各个命令加入到事务命令执行数组中以及取消事务三个模块的执行原理,最后一起看下事务的执行过程,代码较长,需要慢慢看。
把一系列命令加入到事务命令数组中以后,客户端执行exec命令就可以把其中的所有命令挨个执行完成了,分析exec命令源码之前,我们应该可以想到redis的逻辑应该就是从客户端的事务命令数组中取出所有命令一个一个执行,源码如下:
void execCommand(client *c) {
int j;
robj **orig_argv;
int orig_argc;
struct redisCommand *orig_cmd;
//标记是否需要把MULTI/EXEC传递到AOF或者slaves节点
int must_propagate = 0;
//标记当前redis节点是否为主节点
int was_master = server.masterhost == NULL;
//如果客户端没有处于事务状态,则返回错误提示信息
if (!(c->flags & CLIENT_MULTI)) {
addReplyError(c,"EXEC without MULTI");
return;
}
//首先对两个需要终止当前事务的条件进行判断
//1.当有WATCH的key被修改时则终止,返回一个nullmultibulk对象
//2.当之前有命令加入事务命令数组出错则终止,例如传入的命令参数数量不对,会返回execaborterr
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
shared.nullmultibulk);
//删除当前事务信息,前面已经分析过,不赘述
discardTransaction(c);
goto handle_monitor;
}
//把watch的key都删除,上面文章已经分析过,不赘述
unwatchAllKeys(c);
//保存当前命令上下文
orig_argv = c->argv;
orig_argc = c->argc;
orig_cmd = c->cmd;
addReplyMultiBulkLen(c,c->mstate.count);
//遍历事务命令数组
for (j = 0; j < c->mstate.count; j++) {
//把事务队列中的命令参数取出赋值给client,因为命令是在client维度执行的
c->argc = c->mstate.commands[j].argc;
c->argv = c->mstate.commands[j].argv;
c->cmd = c->mstate.commands[j].cmd;
//同步事务操作到AOF或者集群中的从节点
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
execCommandPropagateMulti(c);
must_propagate = 1;
}
//执行具体命令
call(c,CMD_CALL_FULL);
//由于命令可以修改参数的值或者数量,因此重新保存命令上下文
c->mstate.commands[j].argc = c->argc;
c->mstate.commands[j].argv = c->argv;
c->mstate.commands[j].cmd = c->cmd;
}
//恢复原始命令上下文
c->argv = orig_argv;
c->argc = orig_argc;
c->cmd = orig_cmd;
//事务执行完成,删除该事务,前面已经分析过,不赘述
discardTransaction(c);
//确保EXEC会进行传递
if (must_propagate) {
int is_master = server.masterhost == NULL;
server.dirty++;
if (server.repl_backlog && was_master && !is_master) {
char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
feedReplicationBacklog(execcmd,strlen(execcmd));
}
}
//monitor命令操作
handle_monitor:
if (listLength(server.monitors) && !server.loading)
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
}
上面就是事务命令执行的整个逻辑,可以先排除集群跟AOF的同步逻辑,专注理解核心逻辑,代码整体逻辑算是比较清晰的,搞明白了前面的几个模块以后,再看执行逻辑就不会太难。
三、redis事务命令总结
通过上、下两篇文章对redis事务各个命令进行了分析,仔细阅读应该可以了解整个事务执行框架,如果有任何问题或者疑惑,欢迎留言评论。
redis源码分析之事务Transaction(下)的更多相关文章
- redis源码分析之事务Transaction(上)
这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...
- Redis源码分析:serverCron - redis源码笔记
[redis源码分析]http://blog.csdn.net/column/details/redis-source.html Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...
- 死磕以太坊源码分析之MPT树-下
死磕以太坊源码分析之MPT树-下 文章以及资料请查看:https://github.com/blockchainGuide/ 上篇主要介绍了以太坊中的MPT树的原理,这篇主要会对MPT树涉及的源码进行 ...
- redis源码分析之发布订阅(pub/sub)
redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...
- redis源码分析之有序集SortedSet
有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...
- Springboot源码分析之事务拦截和管理
摘要: 在springboot的自动装配事务里面,InfrastructureAdvisorAutoProxyCreator ,TransactionInterceptor,PlatformTrans ...
- Redis源码分析(intset)
源码版本:4.0.1 源码位置: intset.h:数据结构的定义 intset.c:创建.增删等操作实现 1. 整数集合简介 intset是Redis内存数据结构之一,和之前的 sds. skipl ...
- Redis源码分析(dict)
源码版本:redis-4.0.1 源码位置: dict.h:dictEntry.dictht.dict等数据结构定义. dict.c:创建.插入.查找等功能实现. 一.dict 简介 dict (di ...
- Redis源码分析系列
0.前言 Redis目前热门NoSQL内存数据库,代码量不是很大,本系列是本人阅读Redis源码时记录的笔记,由于时间仓促和水平有限,文中难免会有错误之处,欢迎读者指出,共同学习进步,本文使用的Red ...
随机推荐
- ping 中的“TTL"是什么意思
简单来说就是表示一个数据包在网络中可以跳跃的结点数据,当该数据为零时本数据包将被抛弃 详细解释看以下引用: TTL (although named as "time" to liv ...
- fuse的mount机制-流程及参数
在bbfs中,传递的参数有两个目录,fuse将一个目录挂载在另一个目录下. 在ssfs中,传递的参数只有一个目录(传递两个目录fuse会出错). 问题:那么fuse的mount机制到底需要几个目录参数 ...
- Opencv与dlib联合进行人脸关键点检测与识别
前言 依赖库:opencv 2.4.9 /dlib 19.0/libfacedetection 本篇不记录如何配置,重点在实现上.使用libfacedetection实现人脸区域检测,联合dlib标记 ...
- 【扬中集训DAY5T1】 交换矩阵
[题目链接] 点击打开链接 [算法] 链表,对于每个点,存它的上,下,左,右分别是谁 [代码] #include<bits/stdc++.h> using namespace std; # ...
- python-----windows下安装face_recognition库
如果直接在cmd命令界面 输入:pip install face_recognition 如下图所示: 如果第一次就会出现一系列的问题,解决此问题就安装如下步骤: 一.如果你本机没有安装vistual ...
- Linux系统之文件传输的几种方式
Linux系统安装好以后以及能上网.能进行软件安装后,接下来可能就需要从其它机器复制一些文件或者把文件复制到其它机器,那么就涉及到文件的传输和共享,下面介绍一下常规的一些文件传输和共享方案. 1.传统 ...
- WebService安全解决方案—简单握手协议
具体方案如下图: 2.解决方案分析 A.SiteA每次向SiteB发送的请求参数都不一样,造成伪造者难以模仿和推敲里面的算法过程. B.伪造者获得了SayHelloRequest的数据,它向SiteB ...
- A. Bus to Udayland
time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...
- E20180403-hm
accompany vt. 陪伴,陪同; 附加,补充; 与…共存; 为…伴奏 synchronous adj. 同时存在[发生]的,同步的 asynchronous adj. 异步的; particu ...
- (水题)洛谷 - P1478 - 陶陶摘苹果(升级版)
https://www.luogu.org/problemnew/show/P1478 没啥好说的…… 居然还漏写一个等于号WA了一发. #include<bits/stdc++.h> u ...