Table of Contents generated with DocToc
Redis通过MULTI
、EXEC
、WATCH
等命令实现事务(transaction)功能。事务提供一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制。在事务执行期间,服务器不会中断事务去执行其他客户端的命令请求。
事务以MULTI
开始,接着是多个命令放入事务之中,最后由EXEC
将这个事务提交(commit)到服务器执行。
一个事务从开始到结束经历三个阶段:
- 事务开始
- 命令入队
- 事务执行
MULTI
命令标志着事务的开始,它将客户端从非事务状态切换到事务状态,即打开客户端状态的flags
属性的REDIS_MULTI
标识:
def MULTI():
client.flags |= REDIS_MULTI
replyOK()
客户端切换到事务状态后,服务器会根据不同的命令执行不同的操作:
EXEC
、DISCARD
、WATCH
、MULTI
其中一个,服务器立即执行该命令。- 否则,服务器将命令放入一个事务队列,然后向客户端返回
QUEUED
回复。
每个Redis客户端都有自己的事务状态,保存在客户端状态的mstate
属性中:
typedef struct redisClient {
multiState mstate;
} redisClient;
typedef struct multiState {
// 事务队列,FIFO顺序
multiCmd *commands;
// 已入队命令计数
int count;
} multiState;
typedef struct multiCmd {
// 参数
robj **argv;
// 参数数量
int argc;
// 命令指正
struct redisCommand *cmd;
} multiCmd;
服务器收到EXEC
命令后,会遍历客户端的事务列表,执行其中的所有命令。最后将执行所得的结果返回给客户端。
def EXEC():
# 创建空白的回复队列
reply_queue = []
# 遍历事务列表中的每个项
for argv, argc, cmd in client.mstate.commands:
# 执行命令
reply = execute_command(cmd, argv, argc)
reply_quque.append(reply)
# 移除 REDIS_MULTI 标识
client.flags &= ~REDIS_MULTI
# 清空客户端的事务状态,清零计数器,释放事务队列
client.mstate.count = 0
release_transaction_queue(client.mstate.commands)
send_reply_to_client(client, reply_queue)
WATCH
命令是个乐观锁,它可以再EXEC
执行之前,监视任意数量的数据库键,并在EXEC
执行时,检查被监视的键是否至少有一个已经被修改过了。如果是,服务器将拒绝执行事务,并返回客户端事务执行失败的空回复。
每个Redis数据库都保存了一个watched_keys
字典,键是某个被WATCH
的数据库键,值是一个链表,记录了所有监视该键的客户端:
typedef struct redisDb {
dict *watched_keys;
} redisDb;
所有对数据库进行修改的命令,执行之后都会调用multi.h/touchWatchKey
函数对watched_keys
字典进行检查。如果被监视的键被修改,那么打开监视该键的客户端的REDIS_DIRTY_CAS
标识,表示该客户端的事务安全性已遭破坏。
服务器收到EXEC
命令后,根据这个客户端是否打开了REDIS_DIRTY_CAS
标识来决定是否执行事务。
Redis的事务总是具有原子性(atomicity)、一致性(consistency)、隔离性(isolation),且当Redis运行在某种特定的持久化模式下,事务也具有耐久性(durability)。
事务的原子性是指,事务中的多个操作当做一个整体来执行,要么执行所有,要么一个也不执行。
Redis的事务与传统关系型数据库事务的区别在于,Redis不支持事务的回滚机制(rollback),即使事务队列中的某个命令执行出现错误,整个事务也会继续执行下去,直到所有命令执行完毕。
事务的一致性是指,如果数据库在事务执行前是一致的,那么执行后,无论事务是否执行成功,数据库也应该是一致的。「一致」是数据符合数据库本身的定义和要求,没有包含非法或无效的错误数据。
Redis通过谨慎的错误检测和简单的设计来保证事务的一致性。
1、入队错误
如果事务在入队命令的过程中,出现了命令不存在,或者命令格式不正确等情况,Redis会拒绝执行该事务。
2、执行错误
执行过程中的错误是不能再入队时被服务器发现的,这些错误只会在命令实际执行时被触发。事务的执行过程中出现错误,服务器也不会中断事务的执行,而是继续执行其他命令,一致性的命令不会被出错的命令影响。
3、服务器停机
执行事务的过程中停机,不管服务器使用的何种持久化模式,Redis总能保持重启后的数据库一致性。
事务的隔离性是指,即使数据库中有多个事务并发执行,各个事务之间不会相互影响,且与串行执行的结果相同。
Redis采用单线程执行事务,所以事务总是以串行的方式执行,也当然具有隔离性。
事务的持久性是指,一个事务执行完毕后,结果已经被保存到永久性存储介质中。即使服务器停机,执行事务所得的结果也不会丢失。
Redis没有为事务提供额外的持久化功能,事务的持久化由Redis使用的持久化模式决定的:
- 无持久化:事务不具持久性,一旦停机,所有服务器的数据都将丢失。
- RDB持久化:只有执行
BGSAVE
才会对数据库进行保存,且异步执行的BGSAVE
不能保证事务数据在第一时间被保存。因此RDB持久化也不能保证事务的持久性。 - AOF持久化,且
appendfsync
选项为always
时:程序执行命令后会调用同步操作,将命令数据保存到硬盘。这时事务是有持久性的。 - AOF持久化,且
appendfsync
选项为everysec
时:每秒一次同步命令数据到硬盘,事务也不具有持久性。 - AOF持久化,且
appendfsync
选项为no
时:程序交由操作系统来决定何时同步到硬盘,事务也不具有持久性。
上一章:18、发布与订阅
下一章:20、Lua脚本