Redis 中的事务管理

事务

事务的定义

事务是指一系列 “要么都执行,要么都不执行” 的逻辑语句;这些语句可以以SQL语句代码块等形式出现。

文章摘选自 《Redis实现与设计》

Redis 中的事务

Redis 通过 MULTI EXEC WATCH 等命令来实现事务功能。

Redis 中的事务指的是,将 将多个命令视为事务,这些命令要么都实现,要么都不实现 最后由 EXEC 命令将这个事务提交给服务器执行。

redis> MULTI
OK

redis> SET "name" "Practical Common Lisp"
QUEUED

redis> GET "name"
QUEUED

redis> SET "author" "Peter Serbel"
QUEUED

redis> GET "author"
QUEUED

redis> EXEC
1) OK
2) "Practical Common Lisp"
3) OK
4) "Peter Seibel"

事务声明

  1. 首先,我们使用 MULTI multi-前缀,定义参与事务的多行代码,即标识着事务的开始,此时,该客户端的状态将从非事务状态切换成事务状态;这一切换是通过在客户端状态的 flags 属性中打开 REDIS_MULTI 标识来完成的。最后,事务的结果将会依次返回,伪代码如下:
def MULTI():
		
	client.flags |= REDIS_MULTI

	replyOK()
  1. 在标识事务开始后,紧接着我们将一系列代码入队,在已经切换成事务状态后,这些命令不一定会立即执行: 1)如果客户端发送的命令是 EXEC DISCARD WATCH 或者 MULTI 四个命令中的其中一个,那么服务器将会立即执行这个命令。2) 如果不是这四个命令中的其中一个,服务端将放入事务队列中,并向客户端返回QUEUED 回复。

以下流程图可以描述出,从客户端传来的命令,服务端会如何处理:
在这里插入图片描述

切换的这个事务状态属性,保存在每个 Redis 客户端中:

typedef struct redisClient {
	//事务状态
	multiState mstate; /* MULTI/EXEC state */
} redisClient;

事务状态包含一个事务队列(命令以先进先出的顺序),以及一个已入队命令的计数器(也可以说是事务队列的长度):

typedef struct multiState {
	//事务队列 FIFO 顺序
	multiCmd *commands;
	
	//已入队命令计数
	int count;
}

这个事务队列 multiCmd 保存了已入队命令的相关信息,包括指向命令实现函数的指针、命令的参数以及参数的数量:

typedef struct multiCmd {
	//参数
	robj **argv;

	//参数数量
	int argc;

	//命令指针
	struct redisCommand *cmd;
} multiCmd;

下图解释了其中的结构:
在这里插入图片描述

事务执行

当一个处于事务状态的客户端向服务器发送 EXEC 命令时,这个 EXEC 命令将立即被服务器执行。服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果全部返回给客户端。

伪代码如下:

def EXEC():
	reply_queue = []
	# 遍历事务队列中的每个项
	# 读取命令的参数 参数的个数 以及要执行的命令
	for argv, argc, cmd in client.mastate.commands:
		# 执行命令并取得命令的返回值
		reply = execute_command(cmd, argv, argc)
		# 将返回值追加到回复队列末尾
		reply_queue.append(reply)
	
	# 标识本次事务即将完成 让客户端回到非事务状态 
	client.flags &= ~REDIS_MULTI
	client.mstate.count = 0
	release_transaction_queue(client.mastate.commands)
	# 将事务的执行结果返回给客户端
	send_reply_to_client(client, reply_queue)

WATCH 监听命令

WATCH 命令是一个乐观锁 (总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。)

它可以在 EXEC 命令执行之前,监视任意数量的数据库键,并在 EXEC 命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并返回给客户端 nil 作为空回复。

在这里插入图片描述
从图中我们可以看到,在客户端A的事务中,当 EXEC 执行时,已经发现所监听的键 name,已经被客户端B修改了,那么此时本次事务得到的结果,会是nil。

如何实现的呢?

其实,每个 Redis 数据库都保存着一个 watched_keys 字典,这个字典的键是某个被 WATCH 命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端:

typedef struct redisDb {
	// 正在被 WATCH 命令监视的键
	dict *watched_keys;
}

服务器接收到一个客户端发来的 EXEC 命令时,服务器会根据这个客户端是否打开了 REDIS_DIRTY_CAS 标识来决定是否执行事务:

  1. 如果 REDIS_DIRTY_CAS 标识已经被打开,那么说明客户端所监视的键中,至少有一个键已经被修改过了,在这种情况下,客户端提交的事务已经不再安全,所以服务器会拒绝执行客户端提交的事务。

在这里插入图片描述

当这个客户端 c10086 向服务器发送 MULTI 命令,并将一个 SET 命令放入事务队列。

就在这时,另一个客户端 c999 向服务器发送了一条 SET 命令,将 “name” 键的值设置成了 “john”:

c999> SET "name" "john"
OK

由于SET命令会导致正在监视 “name” 键的所有客户端的 REDIS_DIRTY_CAS 标识被打开,其中包括客户端 c10086。

当 c10086 向服务器发送 EXEC 命令时候,因为 c10086 的REDIS_DIRTY_CAS 标识被打开。

当 c10086 向服务器发送 EXEC 命令时候,因为 c10086 的 REDIS_DIRTY_CAS 标志已经被打开,所以服务器将拒绝执行它提交的事务。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值