设为首页 收藏本站
查看: 956|回复: 0

[经验分享] 《Redis源码学习笔记》事务

[复制链接]

尚未签到

发表于 2016-12-18 10:54:17 | 显示全部楼层 |阅读模式
《Redis源码学习笔记》文章列表
Redis中的事务,提供了一种“将多个命令打包并且一次执行”的方式;
当用户输入MULTI命令时,就打开了客户端REDIS_MULTI选项,客户端从“非事务状态”切换到“事务状态”
DSC0000.png
之后客户端执行的所有命令都不会被Redis立即执行,而是放到客户端的“命令队列”里去(服务器返回QUEUED字样,表示命令已经入队),当客户端发出EXEC命令(表示客户端需要执行事务),这时候Redis从客户端的“命令队列”里依次取出命令执行,eg:
redis 127.0.0.1:6379> multi  # 客户端进入事务状态
OK
redis 127.0.0.1:6379> set name diaocow
QUEUED     # 命令已经入队
redis 127.0.0.1:6379> set age  25
QUEUED
redis 127.0.0.1:6379> get name
QUEUED
redis 127.0.0.1:6379> get country
QUEUED
redis 127.0.0.1:6379> exec# 执行事务
1) OK
2) OK
3) "diaocow"
4) (nil)
用一幅图来总结客户端的命令执行流程就是:
DSC0001.png
刚才例子中所对应的命令队列:
DSC0002.png
关于命令队列的更多细节,请看:multi.c/queueMultiCommand函数
前面说到,当客户端处于“事务”状态,所以命令都不会立即执行,而是被放到“命令队列”缓存起来,其实不准确!当客户端处于“事务”状态下,如果遇到下面几个命令,依然会立即执行:
MULTIRedis返回错误,不允许事务嵌套
DISCARD丢弃当前事务(该命令只能在事务状态下使用,否则Redis返回错误)
EXEC执行事务, 把“命令队列”里的命令依次取出、执行,并且把执行结果放入到一个“回答队列”中,当所有命令执行完后,Redis把这个“回答队列”发送给客户端(该命令只能在事务状态下使用,否则Redis返回错误)
WATCH监视某些键,如果在事务执行期间,有一个“监视”的键被修改,那么事务执行失败(该命令必须在MULTI命令之前执行,否则Redis返回错误)

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

DISCARD和EXEC命令比较容易理解,下面我们重点看下WATCH命令的作用以及实现原理:
WATCH命令用来在事务开始之前,监视任意数量的键,当调用EXEC命令执行事务时,如果其中任意一个键被其他客户端修改,那么整个事务将不再执行,直接返回失败,eg:
DSC0003.png
那这一切Redis是怎么实现的呢?
原理:RedisDb中维护了一个watched_keys字典,字典的键就是这个数据库中被“监视”的键,键值就是所有“监视”该键的客户端列表(list类型)
DSC0004.png
(上图只是字典的简化画法,若严格按照字典结构画不仅较为麻烦并且不利于阐述主要思想;关于redis字典详情,请参看 字典章节)
当一个客户端执行watch命令时:
a. Redis会把它加入到被“监视”键的客户端列表中;
b. 同时,客户端自己也会维护一个watched_keys列表,用来保存自己所有监视的键(这个属性有什么用?是不是和RedisDb中的watched_keys属性有重叠?我们稍后会说)
当任何一个会触发键内容变更的命令执行后(譬如set),touchWatchedKey函数会被调用:它检查数据库的watched_keys字典,看该键是否正在被“监视”,如果有,那么该键所关联的所有客户端列表都将打开REDIS_DIRTY_CAS选项(事务被破坏),然后当客户端执行EXEC命令时,如果发现REDIS_DIRTY_CAS选项打开,则事务执行失败,整个过程用伪代码表示就是:
def touchWatchedKey(redisDb, key):
# 获取监视该键的客户端列表
client_list = redisDb.watched_keys.get(key)
if client_list is None: return
# 打开客户端的REDIS_DIRTY_CAS选项(告诉它们事务已经被破坏)
for client in client_list:
client.flag &= REDIS_DIRTY_CAS
更多细节请看:multi.c/touchWatchedKey函数
刚才我们还提到过,客户端自己也维护了一个watched_keys属性——用来保存自己所监视的键,那么这个属性有什么用呢? 我自己觉得是出于效率考虑的:
1. 防止监视相同的键,当Redis发现该客户端已经监视了某个键,则跳过该键,不做任何处理;
2. 当某个客户端(譬如A)执行完事务(或成功或失败),客户端需要清除自己之前所有监视的键,如果客户端自己没有维护自己监视了哪些键,那么Redis就必须遍历整个RedisDb.watched_keys字典,然后在每一个客户端列表中查找A并删除,效率非常低下,但若客户端自己维护了所监视键的列表,那么就不在需要遍历整个字典做清除,eg伪代码:

def unwatchAllKeys(client):
# 若客户端没有监视任何键,则立即返回
if len(client.watched_keys) == 0 : return
# 遍历客户端监视的键
for wk in client.watched_keys.copy():
# 取出被监视键关联的客户端列表
client_list = redisDb.watched_keys.get(wk)
# 删除客户端
client_list.del(client)
client.watched_keys.del(wk)
更多细节请看:multi.c/unwatchAllKeys函数
总结:
1. 熟悉事务相关命令:multi discard exec watch
2. 了解事务执行原理
3. 了解watch命令作用以及实现原理

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-315943-1-1.html 上篇帖子: Redis 配置文件参数说明 下篇帖子: redis.conf 配置参数说明
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表