这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易阅读。因此把事务这个模块整理成上下两篇文章进行总结。
原文地址:www.jianshu.com/p/acb97d620…
这篇文章我们重点分析一下redis事务命令中的两个辅助命令:watch跟unwatch。
一、redis事务辅助命令简介
依然从server.c文件的命令表中找到相应的命令以及它们对应的处理函数。
1 | 复制代码//watch,unwatch两个命令我们把它们叫做redis事务辅助命令 |
- watch,用于客户端关注某个key,当这个key的值被修改时,整个事务就会执行失败(注:该命令需要在事务开启前使用)。
- unwatch,用于客户端取消已经watch的key。
用法举例如下:
clientA
1 | 复制代码127.0.0.1:6379> watch a |
clientB
1 | 复制代码127.0.0.1:6379> set a aa |
二、redis事务辅助命令源码分析
在看具体执行函数之前首先了解几个数据结构:
1 | 复制代码//每个客户端对象中有一个watched_keys链表来保存已经watch的key |
关于事务的几个命令所对应的函数都放在了multi.c文件中。
一起看下watch命令对应处理函数的源码:
1 | 复制代码void watchCommand(client *c) { |
整个watch的数据结构比较复杂,我这里画了一张图方便理解:
watch数据结构
简单解释一下上面的图,首先redis把每个客户端连接包装成了一个client对象,上图中db,watch_keys就是其中的两个字段(client对象里面还有很多其他字段,包括上篇文章中提到的pub/sub)。
- db字段指向给该client对象分配的储存空间,db对象中也含有一个watched_keys字段,是字典类型(也就是哈希表),以想要watch的key做key,存储的链表则是所有watch该key的客户端。
- watch_keys字段则是一个链表类型,每个节点类型为watch_key,其中包含两个字段,key表示watch的key,db则指向了当前client对象的db字段,如上图。
看完watch命令的源码以后,再来看一下unwatch命令,如果搞明白了上面提到的两套数据结构,那么看unwatch的源码应该会比较容易,毕竟就是删除数据结构中对应的内容。
1 | 复制代码void unwatchCommand(client *c) { |
最后我们考虑一下watch机制的触发时机,现在我们已经把想要watch的key加入到了watch的数据结构中,可以想到触发watch的时机应该是修改key的内容时,通知到所有watch了该key的客户端。
感兴趣的用户可以任意选一个修改命令跟踪一下源码,例如set命令,我们发现所有对key进行修改的命令最后都会调用touchWatchedKey()函数,而该函数源码就位于multi.c文件中,该函数就是触发watch机制的关键函数,源码如下:
1 | 复制代码//这里入参db就是客户端对象中的db,上文已经提到,不赘述 |
跟我们猜测的一样,就是每当key的内容被修改时,则遍历所有watch了该key的客户端,设置相应的状态为CLIENT_DIRTY_CAS。
三、redis事务辅助命令总结
上面就是redis事务命令中watch,unwatch的实现原理,其中最复杂的应该就是watch对应的那两套数据结构了,跟之前的pub/sub类似,都是使用链表+哈希表的结构存储,另外也是通过修改客户端的状态位FLAG来通知客户端。
代码比较多,而且C++代码看上去会比较费劲,需要慢慢读,反复读。
本文转载自: 掘金