《Redis源码学习笔记》事务

http://diaocow.iteye.com/blog/1935092


Redis中的事务,提供了一种“将多个命令打包并且一次执行”的方式; 

当用户输入MULTI命令时,就打开了客户端REDIS_MULTI选项,客户端从“非事务状态”切换到“事务状态” 

 

之后客户端执行的所有命令都不会被Redis立即执行,而是放到客户端的“命令队列”里去(服务器返回QUEUED字样,表示命令已经入队),当客户端发出EXEC命令(表示客户端需要执行事务),这时候Redis从客户端的“命令队列”里依次取出命令执行,eg: 

Shell代码   收藏代码
  1. redis 127.0.0.1:6379> multi  # 客户端进入事务状态  
  2. OK  
  3. redis 127.0.0.1:6379> set name diaocow     
  4. QUEUED                       # 命令已经入队  
  5. redis 127.0.0.1:6379> set age  25  
  6. QUEUED  
  7. redis 127.0.0.1:6379> get name  
  8. QUEUED  
  9. redis 127.0.0.1:6379> get country  
  10. QUEUED  
  11. redis 127.0.0.1:6379> exec   # 执行事务  
  12. 1) OK  
  13. 2) OK  
  14. 3"diaocow"  
  15. 4) (nil)  

用一幅图来总结客户端的命令执行流程就是: 

 

刚才例子中所对应的命令队列: 
 

关于命令队列的更多细节,请看:multi.c/queueMultiCommand函数 

前面说到,当客户端处于“事务”状态,所以命令都不会立即执行,而是被放到“命令队列”缓存起来,其实不准确!当客户端处于“事务”状态下,如果遇到下面几个命令,依然会立即执行: 

MULTIRedis返回错误,不允许事务嵌套
DISCARD丢弃当前事务(该命令只能在事务状态下使用,否则Redis返回错误)
EXEC执行事务, 把“命令队列”里的命令依次取出、执行,并且把执行结果放入到一个“回答队列”中,当所有命令执行完后,Redis把这个“回答队列”发送给客户端(该命令只能在事务状态下使用,否则Redis返回错误)
WATCH监视某些键,如果在事务执行期间,有一个“监视”的键被修改,那么事务执行失败(该命令必须在MULTI命令之前执行,否则Redis返回错误)

命令实现伪代码: 
Python代码   收藏代码
  1. def multiCommand(client):  
  2.     # 不允许事务嵌套  
  3.     if client.flag & REDIS_MULTI:  
  4.         return "error"  
  5.     client.flag &= REDIS_MULTI  
  6.   
  7. def discardCommand(client):  
  8.     # discard命令只能在事务状态下使用  
  9.     if not (client.flags & REDIS_MULTI):  
  10.         return "error"  
  11.     # 重置客户端的事务状态(1.释放命令队列;2.重置客户端为非事务状态;3.取消之前监视的所有键)  
  12.     resetClientMultiState(client)  
  13.   
  14. def execCommand(client):  
  15.     # exec命令只能在事务状态下使用  
  16.     if not (client.flags & REDIS_MULTI):  
  17.         return "error"  
  18.     # 若某些监视的键被修改或者命令入队错误(命令不存在?参数错误?),则事务执行失败  
  19.     if client.flag & (REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC):  
  20.         resetClientMultiState()  
  21.         return "error"   
  22.     # 依次执行命令队列里的命令  
  23.     for cmd, argc, argv in client.multiState.commands:  
  24.         result.add(call(cmd, argc, argv))  
  25.     resetClientMultiState(client)  
  26.     return result  

DISCARD和EXEC命令比较容易理解,下面我们重点看下WATCH命令的作用以及实现原理: 

WATCH命令用来在事务开始之前,监视任意数量的键,当调用EXEC命令执行事务时,如果其中任意一个键被其他客户端修改,那么整个事务将不再执行,直接返回失败,eg: 

 

那这一切Redis是怎么实现的呢?  

原理:RedisDb中维护了一个watched_keys字典,字典的键就是这个数据库中被“监视”的键,键值就是所有“监视”该键的客户端列表(list类型) 

 
(上图只是字典的简化画法,若严格按照字典结构画不仅较为麻烦并且不利于阐述主要思想;关于redis字典详情,请参看 字典章节)  

当一个客户端执行watch命令时: 
a. Redis会把它加入到被“监视”键的客户端列表中; 
b. 同时,客户端自己也会维护一个watched_keys列表,用来保存自己所有监视的键(这个属性有什么用?是不是和RedisDb中的watched_keys属性有重叠?我们稍后会说) 

当任何一个会触发键内容变更的命令执行后(譬如set),touchWatchedKey函数会被调用:它检查数据库的watched_keys字典,看该键是否正在被“监视”,如果有,那么该键所关联的所有客户端列表都将打开 REDIS_DIRTY_CAS 选项(事务被破坏),然后当客户端执行EXEC命令时,如果发现 REDIS_DIRTY_CAS 选项打开,则事务执行失败,整个过程用伪代码表示就是: 

Python代码   收藏代码
  1. def touchWatchedKey(redisDb, key):  
  2.     # 获取监视该键的客户端列表  
  3.     client_list = redisDb.watched_keys.get(key)  
  4.     if client_list is Nonereturn  
  5.     # 打开客户端的REDIS_DIRTY_CAS选项(告诉它们事务已经被破坏)  
  6.     for client in client_list:  
  7.         client.flag &= REDIS_DIRTY_CAS   

更多细节请看:multi.c/touchWatchedKey函数 

刚才我们还提到过,客户端自己也维护了一个watched_keys属性——用来保存自己所监视的键,那么这个属性有什么用呢? 我自己觉得是出于效率考虑的: 

1. 防止监视相同的键,当Redis发现该客户端已经监视了某个键,则跳过该键,不做任何处理; 
2. 当某个客户端(譬如A)执行完事务(或成功或失败),客户端需要清除自己之前所有监视的键,如果客户端自己没有维护自己监视了哪些键,那么Redis就必须遍历整个RedisDb.watched_keys字典,然后在每一个客户端列表中查找A并删除,效率非常低下,但若客户端自己维护了所监视键的列表,那么就不在需要遍历整个字典做清除,eg伪代码: 
Python代码   收藏代码
  1. def unwatchAllKeys(client):  
  2.     # 若客户端没有监视任何键,则立即返回  
  3.     if len(client.watched_keys) == 0 : return  
  4.     # 遍历客户端监视的键  
  5.     for wk in client.watched_keys.copy():  
  6.         # 取出被监视键关联的客户端列表  
  7.         client_list = redisDb.watched_keys.get(wk)  
  8.         # 删除客户端  
  9.         client_list.del(client)  
  10.         client.watched_keys.del(wk)  

更多细节请看:multi.c/unwatchAllKeys函数 

总结:  
1. 熟悉事务相关命令:multi discard exec watch 
2. 了解事务执行原理 
3. 了解watch命令作用以及实现原理 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值