源码是redis5版本,在笔记7中,找到了命令数组redisCommandTable
先从最简单的set命令开始看,如何进行最简单的键值对进行set。
setCommand命令实现是在t_string.c文件中。
这里主要是处理set if exist ,set if not exist,set expire 这三种情况。
另外对set命令的value进行redisObject
对象构建。
/* SET key value [NX] [XX] [EX ] [PX ] */
void setCommand(client *c) {
int j;
robj *expire = NULL;
int unit = UNIT_SECONDS;
// 处理标识
/**
* OBJ_SET_NO_FLAGS 0 啥也不带 就是最基础的set key value
* OBJ_SET_NX (1<<0) 当key不存在的时候才设置
* OBJ_SET_XX (1<<1) 当key存在的时候才设置
* OBJ_SET_EX (1<<2) 带过期时间 单位是秒
* OBJ_SET_PX (1<<3) 带过期时间 单位毫秒
*/
int flags = OBJ_SET_NO_FLAGS;
for (j = 3; j < c->argc; j++) {
// ---- 省略对exist和expire的额外参数解读----
}
/**
* typedef struct redisObject {
* unsigned type:4;
* unsigned encoding:4;
* unsigned lru:LRU_BITS;
* int refcount;
* void *ptr;
* } robj;
*/
// 对值进行字符串编码,看是通过OBJ_ENCODING_EMBSTR 进行构建还是通过 OBJ_ENCODING_RAW
c->argv[2] = tryObjectEncoding(c->argv[2]);
// 进行set命令执行
setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}
/**
*
* @param c 客户端对象
* @param flags 对exist的判断和对过期时间的使用标识
* @param key 键
* @param val 值
* @param expire 过期时间
* @param unit 过期时间单位
* @param ok_reply 成功响应
* @param abort_reply 中止响应
*/
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
long long milliseconds = 0; /* initialized to avoid any harmness warning */
if (expire) {
// 过期时间读取与校验
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
return;
if (milliseconds <= 0) {
addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
return;
}
if (unit == UNIT_SECONDS) milliseconds *= 1000;
}
// 针对OBJ_SET_NX 与 OBJ_SET_XX 这两种情况不匹配场景
if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
(flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
{
addReply(c, abort_reply ? abort_reply : shared.nullbulk);
return;
}
// 设置键值对
setKey(c->db,key,val);
// 修改+1
server.dirty++;
// 设置过期时间
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
"expire",key,c->db->id);
addReply(c, ok_reply ? ok_reply : shared.ok);
}
void setKey(redisDb *db, robj *key, robj *val) {
//根据key在hash表中找元素,没找到就新增,找到了就覆盖。
if (lookupKeyWrite(db,key) == NULL) {
//新增
dbAdd(db,key,val);
}else {
// 覆盖
dbOverwrite(db,key,val);
}
// 值的引用计数统计
incrRefCount(val);
// 移除过期key
removeExpire(db,key);
// 标记key的值被修改
signalModifiedKey(db,key);
}
lookupKeyWrite
robj *lookupKeyWrite(redisDb *db, robj *key) {
// 如果key已过期,进行过期处理
expireIfNeeded(db,key);
// 去数据库里找key
return lookupKey(db,key,LOOKUP_NONE);
}
<br/>
lookupKey
在db中查找指定key的值去db中找到键值对所在的字典,然后通过key去字典中找到键值对所在的索引位取出链表进行比较并返回键一致的字典元素对象dictEntry。
这里其实就需要字典相关知识了,在下一篇笔记中,将从redisDb数据库对象的创建引入到dict的使用。也就是字典数据结构dict的使用。
robj *lookupKey(redisDb *db, robj *key, int flags) {
// 找到键值对
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de);
/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
if (server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
!(flags & LOOKUP_NOTOUCH))
{
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
//
updateLFU(val);
} else {
val->lru = LRU_CLOCK();
}
}
return val;
} else {
return NULL;
}
}
其实setCommand命令相对来说还是比较容易理解。主要是在字典方面的操作。