Skip to content

Latest commit

 

History

History
181 lines (118 loc) · 7.66 KB

19、事务.md

File metadata and controls

181 lines (118 loc) · 7.66 KB

Table of Contents generated with DocToc

Redis通过MULTIEXECWATCH等命令实现事务(transaction)功能。事务提供一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制。在事务执行期间,服务器不会中断事务去执行其他客户端的命令请求。

事务以MULTI开始,接着是多个命令放入事务之中,最后由EXEC将这个事务提交(commit)到服务器执行。

19.1 事务的实现

一个事务从开始到结束经历三个阶段:

  1. 事务开始
  2. 命令入队
  3. 事务执行

事务开始

MULTI命令标志着事务的开始,它将客户端从非事务状态切换到事务状态,即打开客户端状态的flags属性的REDIS_MULTI标识:

def MULTI():
    client.flags |= REDIS_MULTI
    replyOK()

命令入队

客户端切换到事务状态后,服务器会根据不同的命令执行不同的操作:

  • EXECDISCARDWATCHMULTI其中一个,服务器立即执行该命令。
  • 否则,服务器将命令放入一个事务队列,然后向客户端返回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)

19.2 WATCH 命令的实现

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

使用 WATCH 命令监视数据库键

每个Redis数据库都保存了一个watched_keys字典,键是某个被WATCH的数据库键,值是一个链表,记录了所有监视该键的客户端:

typedef struct redisDb {
  dict *watched_keys;
} redisDb;

监视机制的触发

所有对数据库进行修改的命令,执行之后都会调用multi.h/touchWatchKey函数对watched_keys字典进行检查。如果被监视的键被修改,那么打开监视该键的客户端的REDIS_DIRTY_CAS标识,表示该客户端的事务安全性已遭破坏。

判断事务是否安全

服务器收到EXEC命令后,根据这个客户端是否打开了REDIS_DIRTY_CAS标识来决定是否执行事务。

19.3 事务的ACID性质

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脚本