小编相信大家都或多或少用过redis,如果你没用过,那你是不是就得emo一会了,这么好用的东西都没接触过,小编只想说,你们公司还缺不缺人。
今晚这篇文章我们一起来了解一下,redis的set方法究竟是如何运行的,小编将带大家一起以redis的set key value方法为例单步调试一下redis(不会的偶尔也直接跳过)。
按惯例,先说调试工具
- clion
- redis6.0.1源码
- 编译环境- cygwin
- cmake
接下来就要开始卷了
注:set key value 具体执行的方法定义在t_string.c文件中。
首先我在客户端执行了 set flkey flvalue 方法。通过我们后端打的断点发现,执行具体set key value功能的方法是 void setGenericCommand。
如下图 (我们从具体的业务方法开始说,之前的socket连接传送具体命令都不属于具体的功能方法,就不提了,有想了解了可以私聊小编一起过几招)。注:小编在下面代码中做了注释。
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 */
//如果通过set ex方法设置了超时时间
if (expire) {
//获取超时时间给milliseconds赋值
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
return;
//判断时间是否小于等于0
if (milliseconds <= 0) {
addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
return;
}
//时间单位转换
if (unit == UNIT_SECONDS) milliseconds *= 1000;
}
// 校验: nx 不能互斥设值, 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.null[c->resp]);
return;
}
//设置key value值
genericSetKey(c,c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
server.dirty++;
//设置超时时间
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
//对订阅了事件set的客户端进行通知
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
//如果设置了超时时间,对订阅了事件expire的客户端进行通知
if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
"expire",key,c->db->id);
addReply(c, ok_reply ? ok_reply : shared.ok);
}
接下来我们主要看genericSetKey方法
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
//当前redis中不存在要插入的key
if (lookupKeyWrite(db,key) == NULL) {
//添加key val
dbAdd(db,key,val);
} else {
//覆盖key val
dbOverwrite(db,key,val);
}
//增加对val的引用计数
incrRefCount(val);
//保留设置前指定key的过期时间,6.0版本之后新增的参数
if (!keepttl) removeExpire(db,key);
if (signal) signalModifiedKey(c,db,key);
}
由于我们的执行的key是不存在的,所以这个时候走dbAdd方法,截图为证接下来我们走到dbAdd方法里看一看
void dbAdd(redisDb *db, robj *key, robj *val) {
//ptr是key字符串的内存首地址指针,将其封装成一个sds对象
sds copy = sdsdup(key->ptr);
//添加key val
int retval = dictAdd(db->dict, copy, val);
//通过宏验证是否添加成功,如果失败直接_exit(1)
serverAssertWithInfo(NULL,key,retval == DICT_OK);
//这里返回给客户端通知先不管,和本次文章的字符串类型不相关
if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET ||
val->type == OBJ_STREAM)
signalKeyAsReady(db, key);
//如果是集群部署,将命令发送给其他节点(slot)
if (server.cluster_enabled) slotToKeyAdd(key->ptr);
}
这个dbAdd方法主要就是通过key对应的指针首地址创建一个sds对象。然后将key对象与value值插入到hash表中。
sds:是redis作者自己写的一个字符串数据结构(简单动态字符串),小编猜测,应该是redis整体是用c语言写的,二c语言中是没有字符串这个数据结构的,如果作者不自己封装一个sds,那就得用c语言中的char[]表示一个字符串。而且char[]用起来也很不方便,要自己记录长度,如果遇到字符串结尾还得自己添加\0,并且对于redis这种内存存储的系统,要尽量做到节省内存,就更不能直接用char数组了。 hash表:redis里定义为dict结构体。其实就是一个哈希表结构。
typedef struct dict {
dictEntry **table;
dictType *type;
unsigned long size;
unsigned long sizemask;
unsigned long used;
void *privdata;
} dict;
typedef struct dictEntry {
void *key;
void *val;
struct dictEntry *next;
} dictEntry;
接下来就是往hash表中(dict )添加具体的元素
/* 往hash表中添加元素 */
int dictAdd(dict *d, void *key, void *val)
{
//这里会为要插入的key申请内存和rehash等操作,都是一些hash表基本的操作 就不看了
dictEntry *entry = dictAddRaw(d,key,NULL);
if (!entry) return DICT_ERR;
//set val
dictSetVal(d, entry, val);
return DICT_OK;
}
redis中set方法 插入一个字符串key val的主流程大概就是这些,大家学废了吗?当然后面添加方法返回后还有一些集群化的代码,我这里是本地起的单机服务,没有走这些代码,就不介绍了。有兴趣的可以私聊小编一起卷。
问君能有几多愁,恰似满屏代码加需求
对一些新技术感兴趣的可以关注公众号:云下凤澜