接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令。

原文地址:http://www.jianshu.com/p/e22615586595

看本篇文章前需要先对上面文章有所了解:

redis源码分析之事务Transaction(上)

一、redis事务核心命令简介

redis事务操作核心命令:

  1. //用于开启事务
  2. {"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0},
  3. //用来执行事务中的命令
  4. {"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0},
  5. //用来取消事务
  6. {"discard",discardCommand,1,"sF",0,NULL,0,0,0,0,0},

在redis中,事务并不具有ACID的概念,换句话说,redis中的事务仅仅是保证一系列的命令按顺序一个一个执行,如果中间失败了,并不会进行回滚操作。

使用redis事务举例如下:

  1. 127.0.0.1:6379> multi
  2. OK
  3. 127.0.0.1:6379> set a a
  4. QUEUED
  5. 127.0.0.1:6379> set b b
  6. QUEUED
  7. 127.0.0.1:6379> set c c
  8. QUEUED
  9. 127.0.0.1:6379> exec
  10. 1) OK
  11. 2) OK
  12. 3) OK
  13. 127.0.0.1:6379>

二、redis事务核心命令源码分析

关于事务的几个命令所对应的函数都放在multi.c文件中。

首先来看一下multi命令,该命令用于标记客户端开启事务状态,因此它做的就是修改客户端状态,代码很简单,如下:

  1. void multiCommand(client *c) {
  2. //如果客户端已经是事务模式,则返回错误提示信息
  3. if (c->flags & CLIENT_MULTI) {
  4. addReplyError(c,"MULTI calls can not be nested");
  5. return;
  6. }
  7. //设置客户端为事务模式
  8. c->flags |= CLIENT_MULTI;
  9. //返回结果
  10. addReply(c,shared.ok);
  11. }

接下来看下redis处理命令逻辑中的一段源码:

这段代码在server.c文件中的processCommand方法中:

  1. //如果客户端处于事务状态且当前执行的命令不是exec,discard,multi跟watch命令中的一个
  2. //则把当前命令加入一个队列
  3. if (c->flags & CLIENT_MULTI &&
  4. c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
  5. c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
  6. {
  7. //加入队列
  8. queueMultiCommand(c);
  9. //返回结果
  10. addReply(c,shared.queued);
  11. } else {
  12. //执行当前命令
  13. call(c,CMD_CALL_FULL);
  14. c->woff = server.master_repl_offset;
  15. if (listLength(server.ready_keys))
  16. handleClientsBlockedOnLists();
  17. }

看入队操作源码前,先来熟悉几个数据结构,redis会把每个连接的客户端封装成一个client对象,该对象中含有大量字段用来保存需要的信息,发布订阅功能也使用对应的字段进行存储,事务当然也不例外,如下:

  1. //每个客户端对象中有一个mstate字段用来保存事务上下文
  2. typedef struct client {
  3. multiState mstate;
  4. }
  5. //事务包装类型
  6. typedef struct multiState {
  7. //当前事务中需要执行的命令数组
  8. multiCmd *commands;
  9. //需要执行的命令数量
  10. int count;
  11. //需要同步复制的最小数量
  12. int minreplicas;
  13. //同步复制超时时间
  14. time_t minreplicas_timeout;
  15. } multiState;
  16. //事务中执行命令的封装类型
  17. typedef struct multiCmd {
  18. //参数
  19. robj **argv;
  20. //参数数量
  21. int argc;
  22. //命令本身
  23. struct redisCommand *cmd;
  24. } multiCmd;

了解了基本的数据结构以后,再来看下入队操作:

  1. void queueMultiCommand(client *c) {
  2. //类型前面有说明
  3. multiCmd *mc;
  4. int j;
  5. //扩容,每次扩容一个命令的大小
  6. c->mstate.commands = zrealloc(c->mstate.commands,
  7. sizeof(multiCmd)*(c->mstate.count+1));
  8. //c++中给数组最后一个元素赋值语法实在是有点难懂...
  9. mc = c->mstate.commands+c->mstate.count;
  10. //初始化mc各个字段
  11. mc->cmd = c->cmd;
  12. mc->argc = c->argc;
  13. mc->argv = zmalloc(sizeof(robj*)*c->argc);
  14. //把参数一个一个拷贝过来
  15. memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);
  16. for (j = 0; j < c->argc; j++)
  17. incrRefCount(mc->argv[j]);
  18. c->mstate.count++;
  19. }

上面是把命令加入事务命令数组的中的逻辑,由于在执行事务过程中也会执行删除事务的操作,因此在看执行事务逻辑之前我们先看下删除事务的实现原理。

当事务执行完成,执行错误或者客户端想取消当前事务,都会跟discard命令有联系,一起看下源码:

  1. void discardCommand(client *c) {
  2. //如果当前客户端没有处于事务状态,则返回错误信息
  3. if (!(c->flags & CLIENT_MULTI)) {
  4. addReplyError(c,"DISCARD without MULTI");
  5. return;
  6. }
  7. //删除事务
  8. discardTransaction(c);
  9. //返回结果
  10. addReply(c,shared.ok);
  11. }
  12. //具体的删除逻辑
  13. void discardTransaction(client *c) {
  14. //释放客户端事务资源
  15. freeClientMultiState(c);
  16. //初始化客户端事务资源
  17. initClientMultiState(c);
  18. //状态位还原
  19. c->flags &= ~(CLIENT_MULTI|CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC);
  20. //取消已watch的key,该函数上面文章中已经进行过分析,不赘述
  21. unwatchAllKeys(c);
  22. }
  23. //释放事务队列中的每个命令
  24. void freeClientMultiState(client *c) {
  25. int j;
  26. for (j = 0; j < c->mstate.count; j++) {
  27. int i;
  28. multiCmd *mc = c->mstate.commands+j;
  29. //挨个释放命令的参数
  30. for (i = 0; i < mc->argc; i++)
  31. decrRefCount(mc->argv[i]);
  32. zfree(mc->argv);
  33. }
  34. //最后释放命令本身
  35. zfree(c->mstate.commands);
  36. }
  37. //事务相关字段设为初始值
  38. void initClientMultiState(client *c) {
  39. c->mstate.commands = NULL;
  40. c->mstate.count = 0;
  41. }

到这里,我们已经了解了开启事务模式,把各个命令加入到事务命令执行数组中以及取消事务三个模块的执行原理,最后一起看下事务的执行过程,代码较长,需要慢慢看。

把一系列命令加入到事务命令数组中以后,客户端执行exec命令就可以把其中的所有命令挨个执行完成了,分析exec命令源码之前,我们应该可以想到redis的逻辑应该就是从客户端的事务命令数组中取出所有命令一个一个执行,源码如下:

  1. void execCommand(client *c) {
  2. int j;
  3. robj **orig_argv;
  4. int orig_argc;
  5. struct redisCommand *orig_cmd;
  6. //标记是否需要把MULTI/EXEC传递到AOF或者slaves节点
  7. int must_propagate = 0;
  8. //标记当前redis节点是否为主节点
  9. int was_master = server.masterhost == NULL;
  10. //如果客户端没有处于事务状态,则返回错误提示信息
  11. if (!(c->flags & CLIENT_MULTI)) {
  12. addReplyError(c,"EXEC without MULTI");
  13. return;
  14. }
  15. //首先对两个需要终止当前事务的条件进行判断
  16. //1.当有WATCH的key被修改时则终止,返回一个nullmultibulk对象
  17. //2.当之前有命令加入事务命令数组出错则终止,例如传入的命令参数数量不对,会返回execaborterr
  18. if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
  19. addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
  20. shared.nullmultibulk);
  21. //删除当前事务信息,前面已经分析过,不赘述
  22. discardTransaction(c);
  23. goto handle_monitor;
  24. }
  25. //把watch的key都删除,上面文章已经分析过,不赘述
  26. unwatchAllKeys(c);
  27. //保存当前命令上下文
  28. orig_argv = c->argv;
  29. orig_argc = c->argc;
  30. orig_cmd = c->cmd;
  31. addReplyMultiBulkLen(c,c->mstate.count);
  32. //遍历事务命令数组
  33. for (j = 0; j < c->mstate.count; j++) {
  34. //把事务队列中的命令参数取出赋值给client,因为命令是在client维度执行的
  35. c->argc = c->mstate.commands[j].argc;
  36. c->argv = c->mstate.commands[j].argv;
  37. c->cmd = c->mstate.commands[j].cmd;
  38. //同步事务操作到AOF或者集群中的从节点
  39. if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
  40. execCommandPropagateMulti(c);
  41. must_propagate = 1;
  42. }
  43. //执行具体命令
  44. call(c,CMD_CALL_FULL);
  45. //由于命令可以修改参数的值或者数量,因此重新保存命令上下文
  46. c->mstate.commands[j].argc = c->argc;
  47. c->mstate.commands[j].argv = c->argv;
  48. c->mstate.commands[j].cmd = c->cmd;
  49. }
  50. //恢复原始命令上下文
  51. c->argv = orig_argv;
  52. c->argc = orig_argc;
  53. c->cmd = orig_cmd;
  54. //事务执行完成,删除该事务,前面已经分析过,不赘述
  55. discardTransaction(c);
  56. //确保EXEC会进行传递
  57. if (must_propagate) {
  58. int is_master = server.masterhost == NULL;
  59. server.dirty++;
  60. if (server.repl_backlog && was_master && !is_master) {
  61. char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
  62. feedReplicationBacklog(execcmd,strlen(execcmd));
  63. }
  64. }
  65. //monitor命令操作
  66. handle_monitor:
  67. if (listLength(server.monitors) && !server.loading)
  68. replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
  69. }

上面就是事务命令执行的整个逻辑,可以先排除集群跟AOF的同步逻辑,专注理解核心逻辑,代码整体逻辑算是比较清晰的,搞明白了前面的几个模块以后,再看执行逻辑就不会太难。

三、redis事务命令总结

通过上、下两篇文章对redis事务各个命令进行了分析,仔细阅读应该可以了解整个事务执行框架,如果有任何问题或者疑惑,欢迎留言评论。

redis源码分析之事务Transaction(下)的更多相关文章

  1. redis源码分析之事务Transaction(上)

    这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...

  2. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  3. 死磕以太坊源码分析之MPT树-下

    死磕以太坊源码分析之MPT树-下 文章以及资料请查看:https://github.com/blockchainGuide/ 上篇主要介绍了以太坊中的MPT树的原理,这篇主要会对MPT树涉及的源码进行 ...

  4. redis源码分析之发布订阅(pub/sub)

    redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...

  5. redis源码分析之有序集SortedSet

    有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...

  6. Springboot源码分析之事务拦截和管理

    摘要: 在springboot的自动装配事务里面,InfrastructureAdvisorAutoProxyCreator ,TransactionInterceptor,PlatformTrans ...

  7. Redis源码分析(intset)

    源码版本:4.0.1 源码位置: intset.h:数据结构的定义 intset.c:创建.增删等操作实现 1. 整数集合简介 intset是Redis内存数据结构之一,和之前的 sds. skipl ...

  8. Redis源码分析(dict)

    源码版本:redis-4.0.1 源码位置: dict.h:dictEntry.dictht.dict等数据结构定义. dict.c:创建.插入.查找等功能实现. 一.dict 简介 dict (di ...

  9. Redis源码分析系列

    0.前言 Redis目前热门NoSQL内存数据库,代码量不是很大,本系列是本人阅读Redis源码时记录的笔记,由于时间仓促和水平有限,文中难免会有错误之处,欢迎读者指出,共同学习进步,本文使用的Red ...

随机推荐

  1. 【转载】u3d游戏客户端架构(---)

    原文:http://blog.csdn.net/xtxy/article/details/8474506 主要是mvc架构, M层为数据层,两个用途:1保存数据:2发送数据更新信息: V层为视图层,两 ...

  2. jsch上传文件功能

    转载:http://www.cnblogs.com/longyg/archive/2012/06/25/2556576.html JSch是Java Secure Channel的缩写.JSch是一个 ...

  3. C#:template

    ylbtech-C#: 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部   作者:ylbtech出处:http://ylbtech.cnbl ...

  4. memset函数学习

    memset是计算机中C/C++语言函数.将s所指向的某一块内存中的后n个 字节的内容全部设置为ch指定的ASCII值, 第一个值为指定的内存地址,块的大小由第三个参数指定,这个函数通常为新申请的内存 ...

  5. Android—— ListView 的简单用法及定制ListView界面

    一.ListView的简单用法 2. 训练目标 1) 掌握 ListView 控件的使用 2) 掌握 Adapter 桥梁的作用 实现步骤: 1)首先新建一个项目, 并让ADT 自动帮我们创建好活动. ...

  6. 7天学完Java基础之7/7

    Object类的toString方法 类Object是类层次结构的根类 每个都使用Object作为超类 所有对象都实现这个类的方法 //这个是Object类的子类,实现了其所有方法 public cl ...

  7. 【算法】LRU算法

    缓存一般存放的都是热点数据,而热点数据又是利用LRU(最近最久未用算法)对不断访问的数据筛选淘汰出来的. 出于对这个算法的好奇就查了下资料. LRU算法四种实现方式介绍 缓存淘汰算法 利用Linked ...

  8. 通过 xshell 连接 ubuntu on windows(WSL)

    装上 ubuntu on windows 后,默认要先打开 cmd, 再运行 bash 进入 ubuntu 的 shell. 但是这个shell很难看,配色不好就算了,还存在各种复制粘贴麻烦. 默认没 ...

  9. Educational Codeforces Round 20 A

    Description You are given matrix with n rows and n columns filled with zeroes. You should put k ones ...

  10. Beautiful People SGU - 199 ZOJ - 2319

    最长上升子序列O(n log n):http://www.cnblogs.com/hehe54321/p/cf-340d.html 题目:https://cn.vjudge.net/problem/Z ...