Redis 源码学习之 Redis 事务Nosql

时间:2023-03-08 16:17:27

Redis事务提供了一种将多个命令请求打包,然后一次性、按照顺序地执行多个命令的机制,并且在事务执行的期间,服务器不会中断事务而去执行其他不在事务中的命令请求,它会把事务中所有的命令都执行完毕才会去执行其他的命令。

How

Redis中提供了multi、discard、exec、watch、unwatch这几个命令来实现事务的功能。

Redis的事务始于multi命令,之后跟着要在事务中执行的命令,终于exec命令或者discard命令。加入事务中的所有命令会原子的执行,中间不会穿插执行其他没有加入事务的命令。

multi、exec和discard

multi命令告诉Redis客户端要开始一个事物,然后Redis会返回一个OK,接下来所有的命令Redis都不会立即执行,只会返回QUEUED结果,直到遇到了exec命令才会去执行之前的所有的命令,或者遇到了discard命令,会抛弃执行之前加入事务的命令。

127.0.0.1:6379> get name

(nil)

127.0.0.1:6379> get gender

(nil)

127.0.0.1:6379> multi

OK

127.0.0.1:6379> set name Slogen

QUEUED

127.0.0.1:6379> set gender male

QUEUED

127.0.0.1:6379> exec

1) OK

2) OK

127.0.0.1:6379> mget name gender

1) "Slogen"

2) "male"

watch

watch命令是Redis提供的一个乐观锁,可以在exec执行之前,监视任意数量的数据库key,并在exec命令执行的时候,检测被监视的key是否至少有一个已经被修改,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。

首先在client1执行下列命令:

127.0.0.1:6379> get name

(nil)

127.0.0.1:6379> watch name

OK

127.0.0.1:6379> multi

OK

127.0.0.1:6379> set name slogen

QUEUED

127.0.0.1:6379> set gender male

QUEUED

127.0.0.1:6379> get name

QUEUED

这个时候client还没有执行exec命令,接下来在client2下执行下面命令修改name:

127.0.0.1:6379> set name rio

OK

127.0.0.1:6379> get name

"rio"

接下来在client1下执行exec命令:

127.0.0.1:6379> exec

(nil)

127.0.0.1:6379> get name

"rio"

从执行结果可以看到,在client1中执行exec命令的时候,Redis会检测到name字段已经被其他客户端修改了,所以拒绝执行事务中所有的命令,直接返回nil表示执行失败。这个时候获取到的name的值还是在client2中设置的rio。

Why

multi

Redis的事务始于multi命令,那么就从multi命令的源代码开始分析。

当Redis接收到客户端发送过来的命令之后会执行multiCommand()这个方法,这个方法在multi.c文件中。

void multiCommand(client *c) {

// 1. 如果检测到flags里面已经包含了CLIENT_MULTI

// 表示对应client已经处于事务的上下文中,返回错误

if (c->flags & CLIENT_MULTI) {

addReplyError(c,"MULTI calls can not be nested");

return;

}

// 2. 开启flags的CLIENT_MULTI标识

c->flags |= CLIENT_MULTI;

// 3. 返回ok,告诉客户端已经成功开启事务

addReply(c,shared.ok);

}

从源代码中可以看到,multiCommand()主要完成下面三件事:

检测发送multi命令的client是否已经处于事务中,如果是则直接返回错误。从这里可以看到,Redis不支持事务嵌套执行。

给对应client的flags标志位中增加MULTI_CLIENT标志,表示已经进入事务中。

返回OK告诉客户端已经成功开启事务。