暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

redis之事务

程序猿研习社 2021-09-06
496

简单来说,redis的事务本质是一个指令集合,在事务提交时服务器会一起执行这些指令;


01

redis事务概述


MULTI、EXEC、DISCARD、WATCH是组成redis事务的四个基础指令;

MULTI

开启一个事务;


EXEC

执行事务;


DISCARD

丢弃事务;


WATCH

监视某一键值对,当客户端监视的某键值对在执行exec时已被修改,那么当前的事务会被取消;


02


实例


    //开启事务
    multi
    OK
    set k pingan
    QUEUED
    set k2 jiankang
    QUEUED
    set k3 xingfu
    QUEUED
    //执行事务
    exec
    1) OK
    2) OK
    3) OK
    get k
    "pingan"
    get k2
    "jiankang"
    get k3
    "xingfu"

    可以看到在执行事务(exec)后,组成事务的三个命令全部执行;


      //开启事务
      multi
      OK
      set k4 hello
      QUEUED
      set k5 hello2
      QUEUED
      //丢弃事务
      discard
      OK
      get k4
      (nil)


      可以看到在执行丢弃事务(discard)后,组成事务的两个命令全部丢弃;


      03


      内部实现


      redis事务的内部实现并不难:为客户端保存一个命令队列结构体,在执行事务时依次执行队列里的命令;


      事务命令

        /*
        * 事务命令
        */
        typedef struct multiCmd {
        参数
            robj **argv;
        参数数量
            int argc;
        命令指针
            struct redisCommand *cmd;
        } multiCmd;



        命令队列结构体

          /*
           * 保存事务命令的结构体
          */
          typedef struct multiState {
          事务队列,FIFO 顺序
              multiCmd *commands;     /* Array of MULTI commands */
          已入队命令计数
          int count; * Total number of MULTI commands */
          int minreplicas; * MINREPLICAS for synchronous replication */
          time_t minreplicas_timeout; * MINREPLICAS timeout as unixtime. */
          } multiState;


          执行开启事务到执行事务整个过程redis服务器状态的变化如下:


          可以在processCommand源码中看见:在multi之后的命令都被保存到队列中

             */
            int processCommand(redisClient *c) {
                ......
            * Exec the command */
            if (c->flags & REDIS_MULTI &&
            c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
            c->cmd->proc != multiCommand && c->cmd->proc != watchCommand) {
            在事务上下文中
            除 EXEC 、 DISCARD 、 MULTI 和 WATCH 命令之外
            其他所有命令都会被入队到事务队列中
            queueMultiCommand(c);
            addReply(c, shared.queued);
            } else {
            执行命令
                    call(c, REDIS_CALL_FULL);
            c->woff = server.master_repl_offset;
            处理那些解除了阻塞的键
            if (listLength(server.ready_keys))
            handleClientsBlockedOnLists();
                }
            return REDIS_OK;
            }


            04


            Watch指令


            redis的watch命令为了让redis拥有CAS特性,CAS的意思是在A修改之前,检查是否有其他人修改,如果其他人已修改,则A修改失败;


            一个没有CAS的例子:


            有CAS的例子:


            客户端结构体redisClient会记录客户端监视的键

              // 被监视的键
              list *watched_keys; 


              当某个键被修改的时候,所有监视改键的客户端都会被标记为REDIS_DIRTY_CAS,标识该键值被修改过,源码如下:


              当用户发出EXEC的时候,在MULTI之后的所有命令都会被执行,从代码上看,如果客户端监视此时已被修改,那么客户端将会被标记REDIS_DIRTY_CAS;

              下面我们实验一下:

                //会话1(key的值为110,减去100剩10)
                decrby key 100
                (integer) 10



                  //会话2中监视key
                  私有redis:0>watch key
                  OK


                  私有redis:0>get key
                  110


                  私有redis:0>watch key
                  OK


                  私有redis:0>multi
                  OK


                  私有redis:0>incrby key 100
                  QUEUED
                  //此时因为在会话1中已经对key进行了修改,所以会话2的事务将被丢弃
                  私有redis:0>exec


                  私有redis:0>get key
                  10



                  用户开启一个事务后会提交多个命令,如果命令在入队过程中出现错误,譬如提交的命令本身不存在,参数错误和内存超额等,都会导致客户端被标记REDIS_DIRTY_EXEC,被标记 REDIS_DIRTY_EXEC 会导致事务被取消;


                  下面我们实验一下:

                    multi
                    OK
                    set key4 hello
                    QUEUED
                    //下面这个命令本身就错了
                    set key5hello2
                    (error) ERR wrong number of arguments for 'set' command
                    exec
                    (error) EXECABORT Transaction discarded because of previous errors.


                    如上两个实验源码中表示为:

                      void execCommand(redisClient *c) {
                          ......
                      /* Check if we need to abort the EXEC because:
                      *
                      * 检查是否需要阻止事务执行,因为:
                      *
                      * 1) Some WATCHed key was touched.
                      * 有被监视的键已经被修改了
                      *
                      * 2) There was a previous error while queueing commands.
                      * 命令在入队时发生错误
                      * (注意这个行为是 2.6.4 以后才修改的,之前是静默处理入队出错命令)
                      *
                      * A failed EXEC in the first case returns a multi bulk nil object
                      * (technically it is not an error but a special behavior), while
                      * in the second an EXECABORT error is returned.
                      *
                      * 第一种情况返回多个批量回复的空对象
                      * 而第二种情况则返回一个 EXECABORT 错误
                      */
                           在这里可以看到被标记为REDIS_DIRTY_CASREDIS_DIRTY_EXEC
                           的客户端都会取消事务
                      if (c->flags & (REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC)) {


                      addReply(c, c->flags & REDIS_DIRTY_EXEC ? shared.execaborterr :
                                                                        shared.nullmultibulk);
                      // 取消事务
                              discardTransaction(c);
                      goto handle_monitor;
                          }
                          ......


                      注意

                      单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

                      官网提示:

                      It's important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands

                      比如有如下操作:

                      multi

                      set k aaa

                      set k2 bbb 如果执行这个指令时出错了,那么set k aaa 照样会执行成功;

                      exec



                      文章转载自程序猿研习社,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                      评论