set
用来存储string的类型数据java
> set key hello
OK
复制代码
get
来获取string类型的值git
> get key
"hello"
复制代码
若是在set
执行的时候,key已经存在,则会覆盖原有key的值github
> set key anotherValue
OK
> get key
"anotherValue"
复制代码
redis.c
中数组 redisCommandTable 为全部暴漏出去的命令列表,以及实现命令的函数指针redis
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的长度来按照不一样的编码方式处理bash
在转码过程当中,传进来的数据会被转成 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长度的不一样使用不一样的编码,同时存储的结构也会不同,以达到优化内存的目的
set源码执行详细过程请戳这里
Redis内部数据结构详解(2)——sds 张铁蕾
Redis内部数据结构详解(3)——robj 张铁蕾