1.2 各种类型数据结构
如果 redisObject 的 type 成员值是 REDIS_HASH 类型的,则当该 hash 的entry 小于配置值: hash-max-zipmap-entries 或者 value 字符串的长度小于hash-max-zipmap-value, 则可以编码成 REDIS_ENCODING_ZIPMAP 类型存储,以节约内存. 否则采用 Dict 来存储.
zipmap 其实质是:用一个字符串数组来依次保存 key 和 value,查询时是依次遍列每个 key-value 对,直到查到为止. 其结构示意图如下:
以下是创建一个zipmap结构的函数。
/* Create a new empty zipmap. */
unsigned char *zipmapNew(void) {
unsigned char *zm = zmalloc(2); //创建了包含两个char的数组。
zm[0] = 0; /* Length */
zm[1] = ZIPMAP_END;
return zm;
}
初始化时只有 2 个字节,第 1 个字节表示 zipmap 保存的 key-value 对的个数(如果 key-value 对的个数超过 254,则一直用 254 来表示, zipmap 中实际保存的 key-value 对个数可以通过 zipmapLen() 函数计算得到).
执行 hset myhash name foo 后
(1)第 1 个字节保存 key-value 对(即 zipmap 的 entry 数量)的数量 1
(2)第 2 个字节保存 key_len 值 4
(3)第 3~6 保存 key “name”
(4)第 7 字节保存 value_len 值 3
(5)第 8 字节保存空闭的字节数 0 (当 该 key 的值被重置时,其新值的长度与旧值的长度不一定相等,如果新值长度比旧值的长度大,则 realloc 扩大内存;
如果新值长度比旧值的长度小,且相差大于 4 bytes ,则 realloc 缩小内存,如果相差小于 4,则将值往前移,并用 empty_len 保存空闲的 byte 数)
(7)第 9~13 字节保存 value 值 “foo”
如果 key 或 value 的长度小于 ZIPMAP_BIGLEN(254),则用一个字节来表示,如果大于 ZIPMAP_BIGLEN(254),则用 5 个字节保存,第一个字节为保存ZIPMAP_BIGLEN(254),后面 4 个字节保存 key 或 value 的长度。
* ziplist 结构
如果 redisObject 的 type 成员值是 REDIS_LIST 类型的,则当该 list 的elem 数小于配置值: hash-max-ziplist-entries 或者 elem_value 字符串的长度小于 hash-max-ziplist-value, 则可以编码成 REDIS_ENCODING_ZIPLIST 类型存储,以节约内存. 否则采用 Dict 来存储。
ziplist 其实质是用一个字符串数组形式的双向链表。
总体结构:
执行:unsigned char *zl = ziplistNew(); 后的内存情况
zl = {11, 0, 0, 0, 10, 0, 0, 0, 0, 0, 255}
1, ziplist header 由 3 个字段组成:
a) ziplist_bytes: 用一个 uint32_t 来保存, 构成 ziplist 的字符串数组的总长度,包括 ziplist header,
b) ziplist_tail_offset: 用一个 uint32_t 来保存,记录 ziplist 的尾部偏移位置.
c) ziplist_length: 用一个 uint16_t 来保存,记录 ziplist 中 elem 的个数
2, ziplist node 也由 3 部分组成:
a) prevrawlen: 保存上一个 ziplist node 的占用的字节数,包括: 保存prevarwlen,currawlen 的字节数和 elem value 的字节数.
b) currawlen&encoding: 当
c) (编码后的)value可以通过 prevrawlen 和 currawlen&encoding 来遍列 ziplist.
ziplist 还能到一些小技巧来节约内存.
(1)len 的存储: 如果 len 小于 ZIP_BIGLEN(254),则用一个字节来保存;否则需要 5 个字节来保存,第 1 个字节存 ZIP_BIGLEN,作为标识符.
(2)value 的存储: 如果 value 是数字类型的,则根据其值的范围转换成ZIP_INT_16B, ZIP_INT_32B 或 ZIP_INT_64B 来保存,否则用 raw 形式保存.
* intset 结构
intset 是用一个有序的整数数组来实现集合(set). struct intset 的定义,如下:
typedef struct intset
{
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
(1)encoding: 来标识数组是 int16_t 类型, int32_t 类型还是 int64_t 类型的数组. 至于怎么先择是那种类型的数组,是根据其保存的值的取值范围来决定的,初始化时是 int16_t, 根据 set 中的最大值在 [INT16_MIN, INT16_MAX] ,[INT32_MIN, INT32_MAX], [INT64_MIN, INT64_MAX]的那个取值范围来动态确定整个数组的类型. 例如 set 一开始是 int16_t 类型,当一个取值范围在[INT32_MIN, INT32_MAX]的值加入到 set 时,则将保存 set 的数组升级成int32_t 的数组.
(2)length: 表示 set 中值的个数
(3)contents: 指向整数数组的指针
创建一个空的intset的函数:
/* Create an empty intset. */
intset *intsetNew(void) {
intset *is = zmalloc(sizeof(intset));
is->encoding = intrev32ifbe(INTSET_ENC_INT16);
is->length = 0;
return is;
}
a) 跳转表定义
是一种数据结构,该结构按照已排序的方式来维护一堆 key 值。
b) 跳转表的特征
1) 由 n 个层次组成
2) 所有的 key 值都会出现在第一层
3) 每层都是一个已排序的链表
4) 若 key 出现在第 i 层,那么该 key 值也出现在低于 i 的各层
5) 一个在第 i 层的元素,也同时指向第 i-1 层的同样的元素
6) top 指针指向最高层的最小 key 值元素
c) 向跳转表中插入数据 x
1)决定 x 要插入的层数 k 值
2)在表中查询 x 的值,并且插入 x 值到低于 k 的所有层
3)若计算出来的层数大于目前的最大层数,那么增加一个新的层,并更新 top 指针,例如,在第 4 层添加元素 119,下图所示:
(1)在 x 参与的所有层查找 x,并且在每一次删除元素 x
(2)若更高层已经为空了,那么删除该层,并更新 top 指针
结构实现:
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
zset 的实现用到了 2 个数据结构: hash_table 和 skip list (跳跃表),其中 hash table 是使用 redis 的 dict 来实现的,主要是为了保证查询效率为O(1),而 skip list (跳跃表) 是用来保证元素有序并能够保证 INSERT 和REMOVE 操作是 O(logn)的复杂度。
* zset 初始化状态
zslCreate()函数用来创建并初如化一个 skiplist。 其中,skiplist 的 level 最大值为ZSKIPLIST_MAXLEVEL=32 层。
ZADD 命令格式:
ZADD key score member
(1) 根据 key 从 redisDb 进行查询,返回 zset 对象。
(2) 以 member 作为 key, score 作为 value,向 zset 的 dict 进行中插入;
(3) 如果返回成功,表明 member 没有在 dict 中出现过,直接向kiplist 进行插入。
(4) 如果步骤返回失败,表明以 member 已经在 dict 中出现过,则需要先从 skiplist 中删除,然后以现在的 score 值重新插入。
插入三个元素后的结构