您的位置:首页 > 数据库 > Redis

redis之zipmap

2016-06-25 17:17 465 查看

类型介绍

zipmap也是redis的存储类型之一,这里面是sting->string的map结构,之所以出现这种形式的类型,主要是为了节省内存空间,采用的序列内存的方式,这种方式的一个问题就是查找的时候不是O(1)的复杂度,而是O(N)的时间复杂度,因此,它的目的是为了应付小规模的数据的,大规模的数据会过渡到hash表结构,这样应该是为了实现效率与空间的一种平衡;

内存格式为:

zmlen–len–“foo”–len–free–“bar”–len–“hello”–len–free–“world”

zmlen,是指zipmap中包含的entry数目,大于254的话,就不起作用了,当做标记开头;

len,是指key的长度,小于254的话占据一个字节,超过的话,后面跟着4个字节来表示长度;

free,空位,一般不会超过4个

代码分析

变量

ZIPMAP_BIGLEN, zipmap的最大长度

ZIPMAP_END, zipmap的结束符

ZIPMAP_VALUE_MAX_FREE, zipmap的free部分最大长度

函数

zipmapNew,新建一个zipmap;

unsigned char *zipmapNew(void) {
unsigned char *zm = zmalloc(2);

zm[0] = 0; /* Length */
zm[1] = ZIPMAP_END;  //结尾标志,0XFF
return zm;
}


zipmapDecodeLength,zipmap解码长度信息;

static unsigned int zipmapDecodeLength(unsigned char *p) {
unsigned int len = *p;

if (len < ZIPMAP_BIGLEN) return len;  //如果长度小于254,无需编码
memcpy(&len,p+1,sizeof(unsigned int)); //长度超过255,获取p指针的后面的4个字节的数据
memrev32ifbe(&len);  //获取该4个自己对应的数据,需要根据大小端判断,来解析长度信息
return len;
}


zipmapEncodeLength,zipmap编码长度信息;

static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
if (p == NULL) {
return ZIPMAP_LEN_BYTES(len);  //如果p为NULL,则单纯返回该长度需要的内存字节数目
} else {
if (len < ZIPMAP_BIGLEN) {
p[0] = len;  //如果小于254,那么无需编码,直接复制给当前字节
return 1;  //主需要一个字节来保存长度信息
} else {
p[0] = ZIPMAP_BIGLEN;
memcpy(p+1,&len,sizeof(len));
memrev32ifbe(p+1);  //需要硬编码到内存
return 1+sizeof(len);  //需要5个字节来保存长度信息
}
}
}


zipmapLookupRaw,zipmap的查找过程;

static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen) {
unsigned char *p = zm+1, *k = NULL;  //zm+1是因为第一位是长度信息
unsigned int l,llen;

while(*p != ZIPMAP_END) {  //还没到结尾
unsigned char free;

/* Match or skip the key */
l = zipmapDecodeLength(p);  //获取当前地址对应的key长度
llen = zipmapEncodeLength(NULL,l);  //获取该长度占用的字节数
if (key != NULL && k == NULL && l == klen && !memcmp(p+llen,key,l)) {
//查看是否能够匹配key信息,如果匹配了
/* Only return when the user doesn't care
* for the total length of the zipmap. */
if (totlen != NULL) {  //这里表示是否需要返回zipmap的整体长度
k = p;
} else {
return p;  //返回k/v当前地址
}
}
//下面是跳过当前kv,进入下一个kv
p += llen+l;
/* Skip the value as well */
l = zipmapDecodeLength(p);
p += zipmapEncodeLength(NULL,l);
free = p[0]; //free的长度
p += l+1+free; /* +1 to skip the free byte */
}
if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1;  //如果需要返回zipmap占据的长度信息,计算
return k;
}


zipmapRequiredLength,计算当前kv所需要的内存大小;

static unsigned long zipmapRequiredLength(unsigned int klen, unsigned
4000
int vlen) {
unsigned int l;

l = klen+vlen+3; //这三个是必有的,
if (klen >= ZIPMAP_BIGLEN) l += 4;  //如果超出254,还需要额外4个字节
if (vlen >= ZIPMAP_BIGLEN) l += 4;
return l;
}


zipmapRawKeyLength,计算当前key占用的内存大小,包括key的长度以及存储该长度使用的数目;

static unsigned int zipmapRawKeyLength(unsigned char *p) {
unsigned int l = zipmapDecodeLength(p);  //获取当前key的长度
return zipmapEncodeLength(NULL,l) + l;  //返回当前key的长度l和该长度所需要的存储字节数之和
}


zipmapRawValueLength,计算存储当前value占用的所有内存数,包括value的长度以及free字段长度和存储value长度占用的字节数;

static unsigned int zipmapRawValueLength(unsigned char *p) {
unsigned int l = zipmapDecodeLength(p);  //获取当前value的长度
unsigned int used;

used = zipmapEncodeLength(NULL,l);  //获取存储value长度使用的内存数
used += p[used] + 1 + l;  //加上一个free字段长度+1
return used;
}


zipmapRawEntryLength,获取当前指针指向的一个kv占用的内存字节数;

static unsigned int zipmapRawEntryLength(unsigned char *p) {
unsigned int l = zipmapRawKeyLength(p);  //先计算当前key占用的字节数
return l + zipmapRawValueLength(p+l);  //再计算当前value占用的字节数
}


zipmapResize,调整当前 zipmap的大小;

static inline unsigned char *zipmapResize(unsigned char *zm, unsigned int len) {
zm = zrealloc(zm, len);
zm[len-1] = ZIPMAP_END;  //尾部置位
return zm;
}


zipmapSet,设置kv,如果当前zipmap已存在该key,则更新对应的value值,这部分主要是涉及内存移动;

unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) {
unsigned int zmlen, offset;
unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen);  //计算该kv所需要的字节数
unsigned int empty, vempty;
unsigned char *p;

freelen = reqlen;
if (update) *update = 0;  //这个是用来表示该key是否之前已经存在,现在是更新操作
p = zipmapLookupRaw(zm,key,klen,&zmlen);  //查找该key
if (p == NULL) {
/* Key not found: enlarge */ //未找到,扩大zipmap
zm = zipmapResize(zm, zmlen+reqlen);
p = zm+zmlen-1; //设置指针跳到zm+zmlen-1
zmlen = zmlen+reqlen;  //当前的长度,zipmap占用的字节数

/* Increase zipmap length (this is an insert) */
if (zm[0] < ZIPMAP_BIGLEN) zm[0]++;  //这里面是当zipmap的entry数目小于254才计数,多的话就不计数了,通过其他方式获取
} else {
/* Key found. Is there enough space for the new value? */
/* Compute the total length: */
if (update) *update = 1;  //更新操作
freelen = zipmapRawEntryLength(p);  //获取该kv之前占据的内存字节数
if (freelen < reqlen) { //如果之前占据的内存数小于现在需要的
/* Store the offset of this key within the current zipmap, so
* it can be resized. Then, move the tail backwards so this
* pair fits at the current position. */
offset = p-zm;  //偏移量
zm = zipmapResize(zm, zmlen-freelen+reqlen); //调整大小
p = zm+offset;  //跳转到现在zipmap之前entry的起始地址

/* The +1 in the number of bytes to be moved is caused by the
* end-of-zipmap byte. Note: the *original* zmlen is used. */
memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));  //将此前entry的后一个entry往后移动,空出来保存该entry的内存空间
zmlen = zmlen-freelen+reqlen;  //获取新的zipmap的带下
freelen = reqlen;  //设置新的该key占用的内存空间
}
}

/* We now have a suitable block where the key/value entry can
* be written. If there is too much free space, move the tail
* of the zipmap a few bytes to the front and shrink the zipmap,
* as we want zipmaps to be very space efficient. */
empty = freelen-reqlen;  //这个是更新操作时,现在的v的长度比以前的小
if (empty >= ZIPMAP_VALUE_MAX_FREE) {  //free的长度超过现在
/* First, move the tail <empty> bytes to the front, then resize
* the zipmap to be <empty> bytes smaller. */
offset = p-zm;
memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));//移动内存空间,将现在的节省下来
zmlen -= empty;
zm = zipmapResize(zm, zmlen);
p = zm+offset;
vempty = 0;
} else {
vempty = empty;  //新的free值
}

/* Just write the key + value and we are done. */
/* Key: */
//上面是指无论哪种情况,都是留出该kv需要的空间,并赋给p可以存储该信息的内存起始地址,下面就是依次将数据写入到内存中
p += zipmapEncodeLength(p,klen);
memcpy(p,key,klen);
p += klen;
/* Value: */
p += zipmapEncodeLength(p,vlen);
*p++ = vempty;
memcpy(p,val,vlen);
return zm;
}


zipmapDel,删除指定key以及对应的内容;

unsigned char *zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int *deleted) {
unsigned int zmlen, freelen;
unsigned char *p = zipmapLookupRaw(zm,key,klen,&zmlen);  //查找该key
if (p) {  //找到
freelen = zipmapRawEntryLength(p);  //获取该key占用内存大小
memmove(p, p+freelen, zmlen-((p-zm)+freelen+1));  //将该kv后面的内容往前移动,覆盖待删除的kv
zm = zipmapResize(zm, zmlen-freelen);  //调整大小

/* Decrease zipmap length */
if (zm[0] < ZIPMAP_BIGLEN) zm[0]--;  //如果zipmap的entry数目小于254,正常计数

if (deleted) *deleted = 1;  //设置删除标记位
} else {
if (deleted) *deleted = 0;
}
return zm;  //返回现在的zipmap的首地址
}


zipmapNext,获取当前kv的值,并返回下一个entry的地址;

unsigned char *zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *klen, unsigned char **value, unsigned int *vlen) {
if (zm[0] == ZIPMAP_END) return NULL;  直接到末尾了,返回NULL
if (key) {  //用于存放当前key的地址
*key = zm;
*klen = zipmapDecodeLength(zm);  //获取当前key的长度
*key += ZIPMAP_LEN_BYTES(*klen);  //当前key的地址
}
zm += zipmapRawKeyLength(zm);  //移动到存储value的地址
if (value) {  //用来保存当前value的地址
*value = zm+1; //跳过长度信息位
*vlen = zipmapDecodeLength(zm);  //获取该value的长度
*value += ZIPMAP_LEN_BYTES(*vlen);  //移动到value的地址
}
zm += zipmapRawValueLength(zm);  //移动到value之后,也就是下一个kv的开始地址
return zm;
}


zipmapGet,获取某个key的value,如果没找到,返回0;

int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen) {
unsigned char *p;

if ((p = zipmapLookupRaw(zm,key,klen,NULL)) == NULL) return 0;  //查找该key,没找到返回0
p += zipmapRawKeyLength(p);  //找到跳过key的长度,移动到value部分
*vlen = zipmapDecodeLength(p);  //获取该value的长度信息
*value = p + ZIPMAP_LEN_BYTES(*vlen) + 1; //跳到value内容的起始地址
return 1;
}


zipmapLen,获取该zipmap的entry的长度;

unsigned int zipmapLen(unsigned char *zm) {
unsigned int len = 0;
if (zm[0] < ZIPMAP_BIGLEN) {
len = zm[0];  //如果长度信息未超过254,则可以直接读取首位获取数目
} else {
unsigned char *p = zipmapRewind(zm);  //这个函数前面没列出,是用来跳过zipmap第一个字节的,直接是第一个key的首地址,和zipmapNext配合来遍历zipmap
while((p = zipmapNext(p,NULL,NULL,NULL,NULL)) != NULL) len++;  //一直到zipmap的结尾,获取长度

/* Re-store length if small enough */
if (len < ZIPMAP_BIGLEN) zm[0] = len; //如果发现长度信息比254小,则可以重置zipmap的第一个位,之所以可能出现这种情况是因为可能删除之前zipmap长度超了,删除过程没有恢复。
}
return len;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redis 缓存