Redis原理:watch命令
在前面的文章中有提到,在multi 前可以通过watch 来观察哪些key,被观察的这些key,会被redis服务器监控,涉及该key被修改时,则在exec 命令执行过程中会被识别出来,exec 就不会再执行命令。
源码分析
// 监控对应的key
void watchCommand(client *c) {
int j;
if (c->flags & CLIENT_MULTI) {
addReplyError(c,"WATCH inside MULTI is not allowed");
return;
}
/* No point in watching if the client is already dirty. */
if (c->flags & CLIENT_DIRTY_CAS) {
addReply(c,shared.ok);
return;
}
for (j = 1; j < c->argc; j++)
// 按指定的key进行监控
watchForKey(c,c->argv[j]);
addReply(c,shared.ok);
}
void watchForKey(client *c, robj *key) {
list *clients = NULL;
listIter li;
listNode *ln;
watchedKey *wk;
/* Check if we are already watching for this key */
// 当前已经监控的key
listRewind(c->watched_keys,&li);
// 检查是否在监控列表
while((ln = listNext(&li))) {
wk = listNodeValue(ln);
if (wk->db == c->db && equalStringObjects(key,wk->key))
return; /* Key already watched */
}
/* This key is not already watched in this DB. Let's add it */
// 获取监控的客户端列表
// db->watched_keys是全局缓存的列表
clients = dictFetchValue(c->db->watched_keys,key);
if (!clients) {
// 没有则创建
clients = listCreate();
dictAdd(c->db->watched_keys,key,clients);
incrRefCount(key);
}
// 记录新的watchKey内容
/* Add the new key to the list of keys watched by this client */
wk = zmalloc(sizeof(*wk));
wk->key = key; // 当前的key
wk->client = c; // 当前的客户端
wk->db = c->db; // 当前的数据库
wk->expired = keyIsExpired(c->db, key); // 是否过期
incrRefCount(key);
listAddNodeTail(c->watched_keys,wk); // 加入客户端的列表
listAddNodeTail(clients,wk); // 加入全局列表
}
watch 命令,做的事情其实也很简单,就是将当前客户端与key 进行关联,并把监控的key加入全局列表中,该全局列表。
当key有变更时,会调用signalModifiedKey
进行通知。
void signalModifiedKey(client *c, redisDb *db, robj *key)
{
// 监控的key进行刷新
touchWatchedKey(db, key);
trackingInvalidateKey(c, key, 1);
}
// 如果在multi之前有watch某些key,则该key的所以异动都会要执行该方法
// 并进行重置client的
void touchWatchedKey(redisDb *db, robj *key) {
list *clients;
listIter li;
listNode *ln;
if (dictSize(db->watched_keys) == 0) return;
clients = dictFetchValue(db->watched_keys, key);
if (!clients) return;
/* Mark all the clients watching this key as CLIENT_DIRTY_CAS */
/* Check if we are already watching for this key */
listRewind(clients,&li);
while((ln = listNext(&li))) {
watchedKey *wk = listNodeValue(ln);
client *c = wk->client;
if (wk->expired) {
/* The key was already expired when WATCH was called. */
if (db == wk->db &&
equalStringObjects(key, wk->key) &&
dictFind(db->dict, key->ptr) == NULL)
{
/* Already expired key is deleted, so logically no change. Clear
* the flag. Deleted keys are not flagged as expired. */
wk->expired = 0;
goto skip_client;
}
break;
}
// 客户端增加标识,告知该客户端有被污染
c->flags |= CLIENT_DIRTY_CAS;
/* As the client is marked as dirty, there is no point in getting here
* again in case that key (or others) are modified again (or keep the
* memory overhead till EXEC). */
// 移除所有的key
unwatchAllKeys(c);
skip_client:
continue;
}
}
该方法会进行相应key的处理,并为持有该key的客户端,将其标识增加CLIENT_DIRTY_CAS
,该标识会在exec命令中判断,如果有,说明watch的key已经被修改,不执行相关的命令。