1. 高层视角解读
redis的事务实现是比较简单的,支持CAS操作,watch命令可以锁定某个key,在事务执行时如果检测到watch的key被修改,事务失败。事务成功执行后,会unwatch掉所有观察的keys。
可以参考《Redis设计与实现》里的事务一章。
事务的实现
2. 底层代码选读
和事务相关的函数都集中在了multi.c文件中
2.1 数据结构
首先回忆一下,一个redis有很多数据库,每个数据库里有一个哈希表存储所有记录,一个哈希表存储所有记录的过期时间,还有一个哈希表存储记录的监控信息。
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
} redisDb;
而客户端存储正在使用的数据库,待执行的事务的命令队列,还有监控信息。
typedef struct redisClient {
redisDb *db; // 当前正在使用的数据库
multiState mstate; /* MULTI/EXEC state */
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
} redisClient;
注意redisDb和redisClient结构体里各有一个watched_keys,但是它们的类型是不同的
如果用java泛型来表示的话代码如下
class redisDb {
Map<robj, redisClient> watched_keys;
}
class redisClient {
List<watchedKey> watched_keys;
}
另外一些结构,watchedKey 里存着监控信息,multiCmd 存着待执行的命令,multiState 则存着一个命令的数组。
typedef struct watchedKey {
// 被监视的键
robj *key;
// 键所在的数据库
redisDb *db;
} watchedKey;
typedef struct multiCmd {
robj **argv;
int argc;
struct redisCommand *cmd;
} multiCmd;
typedef struct multiState {
multiCmd *commands; /* Array of MULTI commands */
int count; /* Total number of MULTI commands */
} multiState;
2.2 watch和unwatch的代码
void watchForKey(redisClient *c, robj *key) {
list *clients = NULL;
listIter li;
listNode *ln;
watchedKey *wk;
// 检查 key 是否存在于数据库的 watched_keys 字典中
clients = dictFetchValue(c->db->watched_keys,key);
// 如果不存在的话,添加它
if (!clients) {
// 值为链表
clients = listCreate();
// 关联键值对到字典
dictAdd(c->db->watched_keys,key,clients);
}
// 1.添加服务端的某个数据库的watched_keys链表中的key节点
listAddNodeTail(clients,c); //服务端的watched_keys里元素类型是redisClient
wk = zmalloc(sizeof(*wk));
wk->key = key;
wk->db = c->db;
// 2.将客户端添加到链表的末尾
listAddNodeTail(c->watched_keys,wk); //客户端的watched_keys里元素类型是watchedKey
}
void unwatchAllKeys(redisClient *c) {
listIter li;
listNode *ln;
// 遍历链表中所有被客户端监视的键
listRewind(c->watched_keys,&li);
while((ln = listNext(&li))) {
list *clients;
watchedKey *wk;
// 从数据库的 watched_keys 字典的 key 键中
// 删除链表里包含的客户端节点
wk = listNodeValue(ln);
// 取出客户端链表
clients = dictFetchValue(wk->db->watched_keys, wk->key);
// 1.删除服务端的某个数据库的watched_keys链表中的key节点
listDelNode(clients,listSearchKey(clients,c));
// 2.删除客户端的watched_keys链表中的key节点
listDelNode(c->watched_keys,ln);
}
}
代码的各个struct之间你引用我,我引用你,有点绕。但也正是用了这个办法,才能够将struct关联起来,然后通过c->db->watched_keys这样的形式来设置一个值。
可以看到watch和unwatch要同时操作服务端和客户端的两个watched_keys。两个watched_keys类型是不一样的,所以代码有点绕。
由于C语言里没有泛型,所以代码看起来不直观,需要深层分析才能看清楚。一不小心编码错了编译没问题,要等运行时才会出错。
而C++则引入了模板,java和c#有泛型来使得代码看起来更直观,也更安全。
运维网声明
1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网 享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com