从源码分析 Redis 事务原理

kenticny

之前的一篇文章对 Redis 事务的使用和特性进行了分析和总结。为了更好的理解 Redis 事务,本文将从 Redis 源码的角度来分析下 Redis 事务的原理。由于 Redis 源码是使用 C 语言写的,而本人的 C 语言功底最多价值五毛钱,所以难免有理解不到之处。

Redis 事务源码分析

Redis 的事务是由 MULTI 命令开始的,所以先来看看 MULTI 命令做了那些事情。

1
2
3
4
5
6
7
8
void multiCommand(client *c) {
if (c->flags & CLIENT_MULTI) {
addReplyError(c,"MULTI calls can not be nested");
return;
}
c->flags |= CLIENT_MULTI;
addReply(c,shared.ok);
}

在执行命令的时候,首先检查客户端连接的状态,如果事务已经启动,则提示错误,否则就将客户端连接状态置为 CLIENT_MULTI,表示事务已启动。

上面在介绍事务的使用的时候提到过,在开启事务以后执行数据操作命令的时候,该命令并不会立即执行,而是会添加到一个队列中,这里我们从代码中看看这部分的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int processCommand(client *c) {
....省略代码....
if (c->flags & CLIENT_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,CMD_CALL_FULL);
c->woff = server.master_repl_offset;
if (listLength(server.ready_keys))
handleClientsBlockedOnKeys();
}
return C_OK;
}

在执行命令的时候对客户端连接状态进行了检查,如果状态为 CLIENT_MULTI (事务已启动),并且执行的命令不是 EXEC, DISCARD, MULTI, WATCH 的时候,Redis 会将这条命令添加到事务队列中,然后返回 QUEUED 信息。

在添加完命令后,下一步则为 EXEC 执行事务,或者 DISCARD 取消事务,我们看下对应命令的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void discardCommand(client *c) {
if (!(c->flags & CLIENT_MULTI)) {
addReplyError(c,"DISCARD without MULTI");
return;
}
discardTransaction(c);
addReply(c,shared.ok);
}

void discardTransaction(client *c) {
freeClientMultiState(c);
initClientMultiState(c);
c->flags &= ~(CLIENT_MULTI|CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC);
unwatchAllKeys(c);
}

DISCARD 的实现很简单,在执行 DISCARD 的时候,首先检查客户端连接状态,如果处于 CLIENT_MULTI,则执行取消事务的操作,包括释放事务状态,重置事务状态,然后重置客户端连接状态,最后解除对所有 key 的监控。

至于 EXEC 命令的代码比较多,这里就不贴出来了,大家可以去 GITHUB 看源码 execCommand 方法,我们这里只对 execCommand 方法具体做了那些事情做一个简述。

在执行 EXEC 命令的时候,首先检查客户端连接状态是否为 CLIENT_MULTI,如果客户端没有进入 CLIENT_MULTI 状态,则返回错误信息;然后检查客户端连接是否处于需要取消事务的状态,这里我们简单回顾下上面我们曾经提到的两种需要取消事务的情况,这里当然不包括我们主动调用 DISCARD 命令取消事务。第一种为在开启事务后输入命令,在向事务队列中加入命令时出错,包括命令名称或者参数错误,这时客户端连接状态会被置为 CLIENT_DIRTY_EXEC;第二种情况为在事务启动前通过 WATCH 命令监控某些 key 的值,当在事务执行是发现监控的值发生了变化,这时客户端连接状态会被置 CLIENT_DIRTY_CAS。如果在执行 EXEC 时客户端连接处于 CLIENT_DIRTY_EXEC 或者 CLIENT_DIRTY_CAS 这两种状态,则执行取消事务操作。

接下来会检查事务中是否有写操作和当前客户端连接的节点是否可写,这里主要针对的是如果当前连接到一个只读的从库,那么如果包含写操作,则事务无法执行,需要取消。

然后就是按顺序执行事务队列中的命令。当然,在执行事务队列之前,Redis 会执行 UNWATCH 命令取消对所有 key 的监控,事务队列中命令执行完成之后,会执行取消事务的相关操作,这里的取消事务其实也就是关闭事务的意思,因为事务中的命令已经执行完成了。

最后就是同步主从节点数据等相关操作了。至此 EXEC 命令执行完毕。从源码分析一个事务的执行过程也就结束了。当然这里我们并没有提到 WATCHUNWATCH 命令的源码,当然大家有兴趣可以直接去看源码,说实话 Redis 源码还是很良心的,注释写的很详细,很方便阅读。

小结

Redis 事务很早的版本就已经加入了,最开始只有 MULTIEXEC,后续又逐渐加入了 DISCARD,WATCH,UNWATCH,当然这也都是 2.x 版本的事了,在后续的版本中 Redis 事务并没有发生打的改变,而且随着 Redis 对于脚本的支持越来越好,很多操作都推荐使用脚本进行,而对于事务而言,脚本也可以实现较为复杂的事务逻辑,所以在未来某个版本中,Redis 事务说不定就会推出舞台了。

至此,对 Redis 事务的介绍也就结束了。虽然都是很基础的操作,但是还是写出来希望能够让刚接触的朋友避开一些误区,毕竟,我本人当年也在误区中被困住过。

  • 本文标题:从源码分析 Redis 事务原理
  • 本文作者:kenticny
  • 创建时间:2018-04-05 21:00:50
  • 本文链接:https://luyun.io/2018/04/05/redis-transactions-sourcecode/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论
此页目录
从源码分析 Redis 事务原理