首页 > 代码库 > Redis-Redi事务注意事项
Redis-Redi事务注意事项
当客户端处于非事务状态下时, 所有发送给服务器端的命令都会立即被服务器执行。但是, 当客户端进入事务状态之后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回 QUEUED , 表示命令已入队。
但其实并不是所有的命令都会被放进事务队列, 其中的例外就是 EXEC 、 DISCARD 、 MULTI 和 WATCH 这四个命令 —— 当这四个命令从客户端发送到服务器时, 它们会像客户端处于非事务状态一样, 直接被服务器执行。
事务的执行可以用如下伪代码描述:
def execute_transaction(): # 创建空白的回复队列 reply_queue = [] # 取出事务队列里的所有命令、参数和参数数量 for cmd, argv, argc in client.transaction_queue: # 执行命令,并取得命令的返回值 reply = execute_redis_command(cmd, argv, argc) # 将返回值追加到回复队列末尾 reply_queue.append(reply) # 清除客户端的事务状态 clear_transaction_state(client) # 清空事务队列 clear_transaction_queue(client) # 将事务的执行结果返回给客户端 send_reply_to_client(client, reply_queue)
不过事务中的命令和普通命令在执行上还是有一点区别的,其中最重要的两点是:
非事务状态下的命令以单个命令为单位执行,前一个命令和后一个命令的客户端不一定是同一个。而事务状态则是以一个事务为单位,执行 exec 后(放入队列不算)会执行事务队列中的所有命令,除非当前事务执行完毕,否则服务器不会中断事务,也不会执行其他客户端的其他命令。执行 exec 后才进入真正执行事务状态(不被中断)。
在非事务状态下,执行命令所得的结果会立即被返回给客户端,而事务则是将所有命令的结果集合到回复队列,再作为 EXEC 命令的结果返回给客户端。
DISCARD 命令用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端, 说明事务已被取消。
Redis 的事务是不可嵌套的, 当客户端已经处于事务状态, 而客户端又再向服务器发送 MULTI 时, 服务器只是简单地向客户端发送一个错误, 然后继续等待其他命令的入队。 MULTI 命令的发送不会造成整个事务失败, 也不会修改事务队列中已有的数据。
WATCH 只能在客户端进入事务状态之前执行, 在事务状态下发送 WATCH 命令会引发一个错误, 但它不会造成整个事务失败, 也不会修改事务队列中已有的数据(和前面处理 MULTI 的情况一样)。该命令用于在事务开始之前监视任意数量的键,当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。 即下图中客户端 A 将执行失败。
在时间 T4 ,客户端 B 修改了 name 键的值, 当客户端 A 在 T5 执行 EXEC 时,Redis 会发现 name 这个被监视的键已经被修改, 因此客户端 A 的事务不会被执行,而是直接返回失败。
WATCH 实现:在每个代表数据库的 redis.h/redisDb 结构类型中, 都保存了一个 watched_keys 字典, 字典的键是这个数据库被监视的键, 而字典的值则是一个链表, 链表中保存了所有监视这个键的客户端。
举个例子, 如果当前客户端为 client10086 , 那么当客户端执行 WATCH key1 key2 时, 前面展示的 watched_keys 将被修改成这个样子:
在任何对数据库键空间(key space)进行修改的命令成功执行之后 (比如 FLUSHDB 、 SET 、 DEL 、 LPUSH 、 SADD 、 ZREM ,诸如此类), multi.c/touchWatchedKey 函数都会被调用 —— 它检查数据库的 watched_keys 字典, 看是否有客户端在监视已经被命令修改的键, 如果有的话, 程序将所有监视这个/这些被修改键的客户端的 REDIS_DIRTY_CAS 选项打开。
当客户端发送 EXEC 命令、触发事务执行时, 服务器会对客户端的状态进行检查:
如果客户端的 REDIS_DIRTY_CAS 选项已经被打开,那么说明被客户端监视的键至少有一个已经被修改了,事务的安全性已经被破坏。服务器会放弃执行这个事务,直接向客户端返回空回复,表示事务执行失败。
如果 REDIS_DIRTY_CAS 选项没有被打开,那么说明所有监视键都安全,服务器正式执行事务。
#参考实现代码
def check_safety_before_execute_trasaction(): if client.state & REDIS_DIRTY_CAS: # 安全性已破坏,清除事务状态 clear_transaction_state(client) # 清空事务队列 clear_transaction_queue(client) # 返回空回复给客户端 send_empty_reply(client) else: # 安全性完好,执行事务 execute_transaction()
Redis 事务的 ACID 性质
Redis-Redi事务注意事项