内容简介:如果在
set
用来存储string的类型数据
> set key hello OK 复制代码
get
来获取string类型的值
> get key "hello" 复制代码
如果在 set
执行的时候,key已经存在,则会覆盖原有key的值
> set key anotherValue OK > get key "anotherValue" 复制代码
set命令执行追踪
redis.c
中数组 redisCommandTable 为所有暴漏出去的命令列表,以及实现命令的函数指针
struct redisCommand redisCommandTable[] = { ... {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0}, {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0}, ... } 复制代码
从这里可以看到 setCommand 即为 set方法的入口。
Code.SLICE.source("c->argv[2] = tryObjectEncoding(c->argv[2]);") .interpretation("在对set的格式做完语法校验,同时取得相应的命令属于 NX/XX/EX/PX/直接set之后,根据value来获取编码"); Code.SLICE.source("setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);") .interpretation("根据实际情况存储k-v对"); 复制代码
在执行Set之前,redis并不是直接将原有传入的string储存,而是先选择了做一层编码,编码之后再来存
Code.SLICE.source("len = sdslen(s);") .interpretation("获取要存储的字符串值的长度,s取值即 redisObject指向的 数据字节指针"); Code.SLICE.source("if (len <= 20 && string2l(s,len,&value))") .interpretation("判断字符串的长度如果小于20并且能够转成long 类型,执行转成long 的逻辑,并结果存储到value"); //... Code.SLICE.source(" o->encoding = OBJ_ENCODING_INT;\n" + " o->ptr = (void*) value;") .interpretation("判定好是可以转成long则设定编码方式为int,同时数据指针就直接存储值"); //... Code.SLICE.source("if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) ") .interpretation("如果字符串长度满足emb的长度条件(44),使用emb编码,使得通过一次内存分配函数的调用就可以拿到连续的内存空间存储 redisObject和 数据 sdshdr"); //... Code.SLICE.source(" emb = createEmbeddedStringObject(s,sdslen(s));") .interpretation("将值使用emb编码后再返回"); //... Code.SLICE.source("if (o->encoding == OBJ_ENCODING_RAW &&\n" + " sdsavail(s) > len/10)\n" + " {\n" + " o->ptr = sdsRemoveFreeSpace(o->ptr);\n" + " }") .interpretation("如果超过了emb限制,则尽量的去较少浪费的空间,将原始的内容直接返回"); //... 复制代码
对于 string 来说,编码是根据value的长度来按照不同的编码方式处理
- 小于等于20 并且能够转换成long,则存储成long类型的数字,指定编码为 OBJ_ENCODING_INT
- 如果长度小于44,则创建EmbeddedString,指定编码为 OBJ_ENCODING_EMBSTR
- 其它情况,指定编码为 OBJ_ENCODING_RAW
在转码过程中,传进来的数据会被转成 redisObject
typedef struct redisObject { unsigned type:4; //指string/list/hash/zset/set unsigned encoding:4; //数据自己的编码格式 unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount; //数据被引用的次数,为0表示可以安全回收这个对象 void *ptr; //对象数据 } robj; 复制代码
实际存储的时候会去检查是否已经有同名的key
Code.SLICE.source(" if (lookupKeyWrite(db,key) == NULL) {\n" + " dbAdd(db,key,val);\n" + " } else {\n" + " dbOverwrite(db,key,val);\n" + " }") .interpretation("如果之前没有存过,就直接添加,否则去覆盖"); 复制代码
每次在查找key的时候,同时也会去检查key是不是已经过期了,满足过期条件的key会被删除,然后再将传进来的string创建 sds 对象,存储起来
//... Code.SLICE.source("char type = sdsReqType(initlen);") .interpretation("根据要新建的字符串获取不同的类型,类型就是宏定义的 0 1 2 3 4这5个取值的类型,代表不同的 sdshdr 结构\n"); //... Code.SLICE.source(" switch(type) {\n" + " case SDS_TYPE_5: {\n" + " *fp = type | (initlen << SDS_TYPE_BITS);\n" + " break;\n" + " }\n" + " case SDS_TYPE_8: {\n" + " SDS_HDR_VAR(8,s);\n" + " sh->len = initlen;\n" + " sh->alloc = initlen;\n" + " *fp = type;\n" + " break;\n" + " }\n" + " case SDS_TYPE_16: {\n" + " SDS_HDR_VAR(16,s);\n" + " sh->len = initlen;\n" + " sh->alloc = initlen;\n" + " *fp = type;\n" + " break;\n" + " }\n" + " case SDS_TYPE_32: {\n" + " SDS_HDR_VAR(32,s);\n" + " sh->len = initlen;\n" + " sh->alloc = initlen;\n" + " *fp = type;\n" + " break;\n" + " }\n" + " case SDS_TYPE_64: {\n" + " SDS_HDR_VAR(64,s);\n" + " sh->len = initlen;\n" + " sh->alloc = initlen;\n" + " *fp = type;\n" + " break;\n" + " }\n" + " }") .interpretation("类型不同创建不同的结构"); 复制代码
字节长度不同创建的结构大小也不同,以 shshdr8 为例
Code.SLICE.source("struct __attribute__ ((__packed__)) sdshdr8 {\n" + " uint8_t len; /* 已经使用的长度 */\n" + " uint8_t alloc; /* 分配的长度 */\n" + " unsigned char flags; /* 3 lsb of type, 5 unused bits */\n" + " char buf[];\n" + "};") .interpretation("len表示使用了的长度,alloc表示分配的空间长度,flags的最低三个bit用来表示header的类型,类型比如 sdshdr8") .interpretation("1:uint8_t指的是 unsigned char ,大小为1字节 char buf[]本身不计算大小,只是真实数据存储的时候,会在 buf最后添加 1个 \0,为了和C做兼容,方便利用C的一些函数") .interpretation("2:__attribute__ ((__packed__)) 是为了告诉编译器,以紧凑的方式存放,不做对齐,redis这样做方便获取数据,比如要拿到flag只需要获取 buf的前一个地址即可"); 复制代码
不同的结构,header 占据空间也就不一样
总结
在读到set命令之后,对于传进来的数据会转换成redisObject,而根据string value长度的不同使用不同的编码,同时存储的结构也会不一样,以达到优化内存的目的
附录
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- LevelDB 源码分析(二):主体结构
- OkHttp源码分析 初识结构篇
- 【以太坊源码解析】-区块数据结构
- mybatis 源码分析(一)框架结构概览
- Python 2.7 源码 - pyc 的结构
- Redis 字符串内部结构源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。