db字段指向给该client对象分配的储存空间,db对象中也含有一个watched_keys字段,是字典类型(也就是哈希表),以想要watch的key做key,存储的链表则是所有watch该key的客户端。
watch_keys字段则是一个链表类型,每个节点类型为watch_key,其中包含两个字段,key表示watch的key,db则指向了当前client对象的db字段,如上图。
看完watch命令的源码以后,再来看一下unwatch命令,如果搞明白了上面提到的两套数据结构,那么看unwatch的源码应该会比较容易,毕竟就是删除数据结构中对应的内容。
void unwatchCommand(client *c) { //取消watch所有key unwatchAllKeys(c); //修改客户端状态 c->flags &= (~CLIENT_DIRTY_CAS); addReply(c,shared.ok); } //取消watch的key void unwatchAllKeys(client *c) { listIter li; listNode *ln; //如果客户端没有watch任何key,则直接返回 if (listLength(c->watched_keys) == 0) return; //注意这里操作的是链表字段 listRewind(c->watched_keys,&li); while((ln = listNext(&li))) { list *clients; watchedKey *wk; //遍历取出该客户端watch的key wk = listNodeValue(ln); //取出所有watch了该key的客户端,这里则是字典(即哈希表) clients = dictFetchValue(wk->db->watched_keys, wk->key); //空指针判断 serverAssertWithInfo(c,NULL,clients != NULL); //从watch列表中删除该客户端 listDelNode(clients,listSearchKey(clients,c)); //如果key只有一个当前客户端watch,则删除 if (listLength(clients) == 0) dictDelete(wk->db->watched_keys, wk->key); //从当前client的watch列表中删除该key listDelNode(c->watched_keys,ln); //减少引用数 decrRefCount(wk->key); //释放内存 zfree(wk); } }最后我们考虑一下watch机制的触发时机,现在我们已经把想要watch的key加入到了watch的数据结构中,可以想到触发watch的时机应该是修改key的内容时,通知到所有watch了该key的客户端。
感兴趣的用户可以任意选一个修改命令跟踪一下源码,例如set命令,我们发现所有对key进行修改的命令最后都会调用touchWatchedKey()函数,而该函数源码就位于multi.c文件中,该函数就是触发watch机制的关键函数,源码如下:
//这里入参db就是客户端对象中的db,上文已经提到,不赘述 void touchWatchedKey(redisDb *db, robj *key) { list *clients; listIter li; listNode *ln; //保存watchkey的字典为空,则返回 if (dictSize(db->watched_keys) == 0) return; //注意这里操作的是字典(即哈希表)数据结构 clients = dictFetchValue(db->watched_keys, key); //如果没有客户端watch该key,则返回 if (!clients) return; //把client赋值给li listRewind(clients,&li); //遍历watch了该key的客户端,修改他们的状态 while((ln = listNext(&li))) { client *c = listNodeValue(ln); c->flags |= CLIENT_DIRTY_CAS; } }跟我们猜测的一样,就是每当key的内容被修改时,则遍历所有watch了该key的客户端,设置相应的状态为CLIENT_DIRTY_CAS。
三、redis事务辅助命令总结上面就是redis事务命令中watch,unwatch的实现原理,其中最复杂的应该就是watch对应的那两套数据结构了,跟之前的pub/sub类似,都是使用链表+哈希表的结构存储,另外也是通过修改客户端的状态位FLAG来通知客户端。
代码比较多,而且C++代码看上去会比较费劲,需要慢慢读,反复读。