redis事务

来源:互联网 发布:51自学网单片机 编辑:程序博客网 时间:2024/06/08 05:26

redis事务

转自:http://blog.csdn.net/u012658346/article/details/51396553

事务的性质

ACID

  一般数据库的事务需要满足ACID四条性质: 
  这里写图片描述

redis事务

  为了保持简单,Redis事务保证了其中的一致性和隔离性; 
  不满足原子性和持久性;
  1)原子性:redis事务在执行的中途遇到错误,不会回滚,而是继续执行后续命令;(违反原子性)
  2)持久性:事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能
  Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
  1)事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  2)事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

命令

下表列出了 redis 事务的相关命令:

序号命令及描述1DISCARD 
取消事务,放弃执行事务块内的所有命令2EXEC 
执行所有事务块内的命令3MULTI 
标记一个事务块的开始4UNWATCH 
取消 WATCH 命令对所有 key 的监视5WATCH key [key …] 
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

实现

  上述事务相关命令的实现都在文件multi.c中 
  一个事务从开始到结束通常可以分为3个阶段: 
  1)事务开始 
  2)命令入队 
  3)事务执行

事务开始(multi)

multi命令标志着事务的开始 ,通过将标志位置为REDIS_MULTI来标志正处于事务阶段

void multiCommand(redisClient *c) {    if (c->flags & REDIS_MULTI) {   //已经处于事务阶段        return;    }    c->flags |= REDIS_MULTI;    addReply(c,shared.ok);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

命令入队

  在server每次收到一个client的请求,对请求进行处理时, 
  1)首先会判断是否需要将命令入队 
  2)如果是,则调用queueMultiCommand将命令加入到队列中 
  3)否则调用call处理命令

int processCommand(redisClient *c) {    /*   ......   */    if (c->flags & REDIS_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,REDIS_CALL_FULL); //处理命令        c->woff = server.master_repl_offset;        if (listLength(server.ready_keys))            handleClientsBlockedOnLists();    }    return REDIS_OK;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  这里写图片描述
  命令入队的实现如下:

void queueMultiCommand(redisClient *c) {    multiCmd *mc;    int j;    c->mstate.commands = zrealloc(c->mstate.commands,            sizeof(multiCmd)*(c->mstate.count+1));  //重新分配内存    mc = c->mstate.commands+c->mstate.count; //队列尾部元素    //将命令copy到c->mstate.commands数组中    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++; //增加队列长度}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  所有命令都是按照从前到后的顺序,保存在c->mstate.commands这个数组中的。 
  这里写图片描述
  这里写图片描述

执行事务

  当server收到exec命令时,就开始执行事务

void execCommand(redisClient *c) {    //如果有watch的key发生变化,或者在命令入队过程中发生错误,则取消事务    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;    }    /* 首先unwatch所有监视的key */    unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */    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++) {        c->argc = c->mstate.commands[j].argc;        c->argv = c->mstate.commands[j].argv;        c->cmd = c->mstate.commands[j].cmd;        call(c,REDIS_CALL_FULL);  //依次执行每个命令        c->mstate.commands[j].argc = c->argc;        c->mstate.commands[j].argv = c->argv;        c->mstate.commands[j].cmd = c->cmd;    }    discardTransaction(c);  //事务执行完成,释放事务}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

取消事务

  通过discard命令取消事务

void discardCommand(redisClient *c) {    if (!(c->flags & REDIS_MULTI)) {  //没有事务要执行,返回        addReplyError(c,"DISCARD without MULTI");        return;    }    discardTransaction(c);  //执行discard操作    addReply(c,shared.ok);}void discardTransaction(redisClient *c) {    freeClientMultiState(c); //释放队列中的所有命令    initClientMultiState(c);  //将事务状态恢复成初始状态    c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC); //设置标志位    unwatchAllKeys(c);  //unwatch所有监视的key}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

watch & unwatch

  对于监视的命令,是通过数据库中的字典c->db->watched_keys实现的。 
  将watch的命令加入到该字典中,并从该字典中移除unwatch的命令 


1 0
原创粉丝点击