`
xpenxpen
  • 浏览: 702840 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

redis源码阅读笔记(13)——事务

阅读更多
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#有泛型来使得代码看起来更直观,也更安全。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics