深入底层C源码 Redis核心设计原理( 五 )


【2】为什么会有int , 因为整型值最大固定是64bit , 其实与指针*ptr占据的大小一致 , 其实把数值存于这里可以减少了对空间的开辟 。代码展示:
//server.c文件中封装了所有的客户端命令//发现set命令会执行setCommand方法【该方法位于t_string.c文件中】 , 直接看核心部分void setCommand(client *c) {....// 完成编码set:keyvaluec->argv[2] = tryObjectEncoding(c->argv[2]);setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);}//该方法位于object.c文件中robj *tryObjectEncoding(robj *o) {long value;sds s = o->ptr;size_t len;/*确保这是一个字符串对象 , 我们在这个函数中编码的唯一类型 。其他类型使用编码的内存高效表示 , 但由实现该类型的命令处理 。* /serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);//只有类型为 原生sds类型 或者embstr类型 ,  还有机会可以进一步编码 , 否则直接返回if (!sdsEncodedObject(o)) return o;// 如果其他地方有应用即当前对象为共享对象 ,  修改范围将扩大 , 所以放弃编码为整形操作if (o->refcount > 1) return o;//判断是否可以把该字符串转化为一个长整型len = sdslen(s);//范围是否在 整型值得表示范围  ,  0 - 2^64,最多不超过20 位if (len <= 20 && string2l(s,len,&value)) {/**如果Redis的配置不要求运行LRU替换算法 , 且转成的long型数字的值又比较小*(小于OBJ_SHARED_INTEGERS , 在目前的实现中这个值是10000) , *那么会使用共享数字对象来表示 。之所以这里的判断跟LRU有关 , 是因为LRU算法要求每个robj有不同的lru字段值 , *所以用了LRU就不能共享robj 。shared.integers是一个长度为10000的数组 , 里面预存了10000个小的数字对象 。*这些小数字对象都是 encoding = OBJ_ENCODING_INT的string robj对象 。** */// 没有设置内存淘汰策略 , 且数字范围在 缓存整型得范围内if ((server.maxmemory == 0 ||!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&value >= 0 &&value < OBJ_SHARED_INTEGERS){decrRefCount(o); // 不需要用额外得对象来存储incrRefCount(shared.integers[value]);return shared.integers[value];// 共享对象} else {// 如果前一步不能使用共享小对象来表示 , 那么将原来的robj编码成encoding = OBJ_ENCODING_INT , 这时ptr字段直接存成这个long型的值 。// 注意ptr字段本来是一个void *指针(即存储的是内存地址) , // 因此在64位机器上有64位宽度 , 正好能存储一个64位的long型值 。这样 , 除了robj本身之外 , 它就不再需要额外的内存空间来存储字符串值 。if (o->encoding == OBJ_ENCODING_RAW) {sdsfree(o->ptr); // 释放空间o->encoding = OBJ_ENCODING_INT;// 用整形编码o->ptr = (void*) value;return o;} else if (o->encoding == OBJ_ENCODING_EMBSTR) {decrRefCount(o);return createStringObjectFromLongLongForValue(value);}}}// 数据长度 小于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44的话 ,  用 embstr 进行编码if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {robj *emb;if (o->encoding == OBJ_ENCODING_EMBSTR) return o;emb = createEmbeddedStringObject(s,sdslen(s));decrRefCount(o);return emb;}trimStringObjectIfNeeded(o);/* Return the original object. */return o;}【3】为什么会有embstr , 代码展示
//CPU读取数据的时候其实是会有一个缓存行的概念(cache line , 通常是64byte的空间) , 也就是一次性读取的大小//而redisObject数据大小为16 bytetypedef struct redisObject {unsigned type:4;//占4 bitunsigned encoding:4;//占4 bitunsigned lru:LRU_BITS; // 占24 bitint refcount;// 4 bytevoid *ptr;// 8 byte} robj;//总空间:4 bit + 4 bit + 24 bit + 4 byte + 8 byte = 16 byte 所以读取是会读【redisObject 16 byte , 及其后面的48byte的数据(但是用不到)】为了节约CPU成本 , 可不可以在创建的时候 , 将数据就存在后面呢?(为什么采用sdshdr8 , 因为最多存44个字符 , sdshdr8可以容纳128个 , 满足条件 , 且消耗最小)struct __attribute__ ((__packed__)) sdshdr8 { // 对应的字符串长度小于 1<<8uint8_t len;//占据1byte , 表示128个uint8_t alloc;//占据1byteunsigned char flags;//占据1bytechar buf[];//以'\0'结尾 , 这个字符也会占据1byte};所以如果把他们都存于一个64byte的内存中是不是读取对象的时候顺便可以把值也拿出来了 , 减少了一次IO 。

经验总结扩展阅读