当前位置:首页 > PHP教程 > PHP总结归纳

redis源代码分析21–事务

redis的事务较简单,并不具备事务的acid的全部特征。主要原因之一是redis事务中的命令并不是立即执行的,会一直排队到发布exec命令才执行所有的命令;另一个主要原因是它不支持回滚,事务中的命令可以部分成功,部分失败,命令失败时跟不在事务上下文执行时

redis的事务较简单,并不具备事务的acid的全部特征。主要原因之一是redis事务中的命令并不是立即执行的,会一直排队到发布exec命令才执行所有的命令;另一个主要原因是它不支持回滚,事务中的命令可以部分成功,部分失败,命令失败时跟不在事务上下文执行时返回的信息类似。不知道在未来会不会提供更好的支持。

我们且来看看现在redis事务的实现。

redis中跟事务相关的主要结构如下所示。每个redisclient的multistate保存了事务上下文要执行的命令。

/* client multi/exec state */
typedef struct multicmd {
    robj **argv;
    int argc;
    struct rediscommand *cmd;
} multicmd;
typedef struct multistate {
    multicmd *commands;     /* array of multi commands */
    int count;              /* total number of multi commands */
} multistate;
typedef struct redisclient {
   ---
    multistate mstate;      /* multi/exec state */
    ---
} redisclient;

client通过发布multi命令进入事务上下文。处于事务上下文的client会设置redis_multi标志,multi命令会立即返回。

static void multicommand(redisclient *c) {
    c->flags |= redis_multi;
    addreply(c,shared.ok);
}

处于事务上下文中的client会将在exec命令前发布的命令排队到mstate,并不立即执行相应命令且立即返回 shared.queued(如果之前参数检查不正确,则会返回出错信息,那就不会排队到mstate中),这在processcommand函数中反映出来(对processcommand的详细解释可参看前面命令处理章节)。queuemulticommand只是简单的扩大mstate数组,并将当前命令加入其中。

static int processcommand(redisclient *c) {
    ---
   /* exec the command */
    if (c->flags & redis_multi && cmd->proc != execcommand && cmd->proc != discardcommand) {
        queuemulticommand(c,cmd);
        addreply(c,shared.queued);
    } else {
        if (server.vm_enabled && server.vm_max_threads > 0 &&
            blockclientonswappedkeys(c,cmd)) return 1;
        call(c,cmd);
    }
    ---
}

当client发布exec命令时,则redis会调用execcommand来执行事务上下文中的命令集合。注意,在此之前,redis会使用execblockclientonswappedkeys提前加载其命令集所需的key(该函数最终是调用前面介绍过的 waitformultipleswappedkeys来加载key)。因为这在命令表cmdtable是这样设置的:

{"exec",execcommand,1,redis_cmd_inline|redis_cmd_denyoom,execblockclientonswappedkeys,0,0,0},

execcommand会检查是不是处于事务上下文,然后使用execcommandreplicatemulti向 slave/monitor/aof(前提是使用这些功能)发送/写入multi命令字,因为multi命令本身没有排队,而execcommand会在执行完后写入exec命令的,必须让exec和multi命令配对,这之后就是调用call依次执行每个命令了。从这里没有检查call的返回就可以看出,如果命令执行失败了,只能由call命令本身返回出错信息,这里并不检查命令执行的成功与否,最后就是清空mstate中的命令字并取消 redis_multi状态了。

static void execcommand(redisclient *c) {
    int j;
    robj **orig_argv;
    int orig_argc;
    if (!(c->flags & redis_multi)) {
        addreplysds(c,sdsnew("-err exec without multirn"));
        return;
    }
    /* replicate a multi request now that we are sure the block is executed.
     * this way we'll deliver the multi/..../exec block as a whole and
     * both the aof and the replication link will have the same consistency
     * and atomicity guarantees. */
    execcommandreplicatemulti(c);
    /* exec all the queued commands */
    orig_argv = c->argv;
    orig_argc = c->argc;
    addreplysds(c,sdscatprintf(sdsempty(),"*%drn",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;
        call(c,c->mstate.commands[j].cmd);
    }
    c->argv = orig_argv;
    c->argc = orig_argc;
    freeclientmultistate(c);
    initclientmultistate(c);
    c->flags &= (~redis_multi);
    /* make sure the exec command is always replicated / aof, since we
     * always send the multi command (we can't know beforehand if the
     * next operations will contain at least a modification to the db). */
    server.dirty++;
}

最后稍微提一下,如果事务上下文执行过程中,redis突然down掉,也就是最后的exec命令没有写入,此时会让 slave/monitor/aof处于不正确的状态。redis会在重启后会检查到这一情况,这是在loadappendonlyfile中完成的。当然这一检测执行的前提是down掉前和重启后都使用aof进行持久化。redis在检测到这一情况后,会退出程序。用户可调用用redis-check- aof工具进行修复。


【说明】本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:)!