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

1. 高层视角解读
redis的事务实现是比较简单的,支持CAS操作,watch命令可以锁定某个key,在事务执行时如果检测到watch的key被修改,事务失败。事务成功执行后,会unwatch掉所有观察的keys。
可以参考《Redis设计与实现》里的[url=http://origin.redisbook.com/en/latest/feature/transaction.html]事务[/url]一章。
[url=http://www.redisbook.com/en/latest/preview/transaction/transaction_implement.html]事务的实现[/url]

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#有泛型来使得代码看起来更直观,也更安全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值