我在计算key
和value
的空间的时候,发现我使用命令获取的和自己算的总是对不上。比如
命令行执行
代码语言:javascript复制local:0>set 5ycode yxkong
"OK"
local:0>OBJECT ENCODING 5ycode
"embstr"
local:0>DEBUG OBJECT 5ycode
"Value at:0x7f9dc6a0e180 refcount:1 encoding:embstr serializedlength:7 lru:14046288 lru_seconds_idle:32"
local:0>memory usage 5ycode
"56"
local:0>Append 5ycode 1
"7"
local:0>OBJECT ENCODING 5ycode
"raw"
local:0>memory usage 5ycode
"66"
我开始手动计算,我的依据是以下的数据结构:
代码语言:javascript复制typedef struct redisObject {
//robj存储的对象类型
unsigned type:4; //4位
// 编码
unsigned encoding:4; //4位
/**
* @brief 24位
* LRU的策略下:lru存储的是 秒级时间戳的低24位,约194天会溢出
* LFU的策略下:24位拆为两块,高16位(最大值65535)低8位(最大值255)
* 高16存储的是 存储的是分钟级&最大存储位的值,要溢出的话,需要65535`$ 约 45天溢出
* 低8位存储的是近似统计位
* 在lookupKey进行更新
*/
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). */
//引用次数,当为0的时候可以释放就,c语言没有垃圾回收的机制,通过这个可以释放空间
int refcount; //4字节
/**
* 指针有两个属性
* 1,指向变量/对象的地址;
* 2,标识变量/地址的长度;
* void 因为没有类型,所以不能判断出指向对象的长度
*/
void *ptr; // 8字节
} robj;//一个robj 占16字节
//因为在客户端解析的时候创建的是sdshdr8
struct __attribute__ ((__packed__)) sdshdr8 {
//1字节 max= 255 已用空间
uint8_t len; /* used */
//1字节 申请的buf的总空间,max255(不包含flags、len、alloc这些)
uint8_t alloc;
// 1字节 max= 255
unsigned char flags;
// 字节数组 1结尾
char buf[];
};//4 n 长度
//key val关系
typedef struct dictEntry {
void *key;//64位系统占8字节 32位系统占4字节
void *val;//64位系统占8字节 32位系统占4字节
struct dictEntry *next; //64位系统占8字节 32位系统占4字节
} dictEntry; //占24字节
开始计算embstr
编码的:
key 是一个sds字符串,共计:10字节:
len alloc flags=3字节
buf[] = 6(5ycode) 1( )=7字节
dictEntry三个指针总和大小
entry:3*8= 24 字节
value 是一个robj sdshdr8: 26字节
robj结构体:16字节
sds中: len alloc flags:3字节
buf[] 6(yxkong) 1( )=7
共计:10 24 26=60
what?
memory usage 5ycode 打印的结果是56字节,手动算出来是60字节?
不对啊,开始撸代码
代码语言:javascript复制db.c
/**
* @brief 将对应的key 和 redisObject对象塞入到全局hash表中
*
* @param db
* @param key
* @param val
*/
void dbAdd(redisDb *db, robj *key, robj *val) {
//生成一个固定长度的sds字符串作为key(因为key没有动态扩容,所以不需要多申请空间)
sds copy = sdsdup(key->ptr);
//添加key,val 到全局hash表
int retval = dictAdd(db->dict, copy, val);
// 输出信息
serverAssertWithInfo(NULL,key,retval == DICT_OK);
if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET)
//list和zset添加数据后会触发signalKeyAsReady(这里应该再加个stream,ps:stream已经单独处理了)
signalKeyAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key);
}
dict.c
/**
* @brief 添加一个key val到对应的hash表
* @param d 目标hash表
* @param key sds字符串 的指针
* @param val robj的指针
* @return int
*/
int dictAdd(dict *d, void *key, void *val)
{
dictEntry *entry = dictAddRaw(d,key,NULL);
if (!entry) return DICT_ERR;
//将val赋值给entry
dictSetVal(d, entry, val);
return DICT_OK;
}
object.c中
/**
* @brief 创建embstr字符串
* 好处是空间连续,只需创建一次
* @param ptr 字符串指针
* @param len 实际字符长度 小于44位
* @return robj* 在这里明确创建了一个SDS_TYPE_8类型的embstr
*/
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
// 申请一个robj sdshrd8 的连续空间
//16 (3 1) len 最大64字节,这里会做内存对齐
robj *o = zmalloc(sizeof(robj) sizeof(struct sdshdr8) len 1);
//字符串结构体开始位置, 1是因为o->type o->encoding=1字节
/**
* 因为o是一个robj类型
* o 1等价于sizeof(o) 1 1表示o结构体的一个单位
* sizeof(o)就是robj类型的结构体大小
* 所以sh的指针就是在申请的内存空间里排除了robj对象的大小
*/
struct sdshdr8 *sh = (void*)(o 1);
o->type = OBJ_STRING;
o->encoding = OBJ_ENCODING_EMBSTR;
/**
* 这块刚开始还有点想不明白,为什么 1?
* 指针是可以在知道结构体类型的情况下,指针是可以自加操作,1个单位表示结构体的大小
* void 指针因为不知道结构体类型,所以无法自加,也可以防止误操作
*/
o->ptr = sh 1;
//占用4字节
o->refcount = 1;
//设置过期策略
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
} else {
o->lru = LRU_CLOCK();
}
//直接已用长度和申请长度打满,后续不让动了
sh->len = len;
sh->alloc = len;
sh->flags = SDS_TYPE_8;
if (ptr == SDS_NOINIT)
sh->buf[len] = '