视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
redis源代码分析21–事务
2020-11-09 13:32:25 责编:小采
文档

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 MULTI\r\n"));
 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(),"*%d\r\n",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工具进行修复。

下载本文
显示全文
专题