Redis对象系统结构 string(字符串),hash(哈希),list(列表),set(集合)及zset(有序集合)。
五种类型对象的构建用到了以上的主要数据结构:简单动态字符串、双端链表、字典、压缩列表、整数集合。
Redis中的键值对,键总是一个字符串对象,值可以是五种对象中的一种。
“字符串键”指key-value 中的value是字符串对象类型
“列表键”指key-value 中的value是列表对象类型
typedef struct redisObject {
// 对象类型(五种对象类型)
unsigned type:4;
// 底层数据结构编码
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用计数
int refcount;
// 指向底层实现数据结构的指针
void *ptr;
} robj;
对象 | 对象type属性值 | TYPE命令输出 |
---|---|---|
字符串对象 | REDIS_STRING | “string” |
列表对象 | REDIS_LIST | “list” |
哈希对象 | REDIS_HASH | “hash” |
集合对象 | REDIS_SET | “set” |
有序集合对象 | REDIS_ZSET | “zset” |
对象的ptr指针指向对象的底层实现数据结构,由对象的encoding属性决定。
编码常量 | 底层数据结构 |
---|---|
REDIS_ENCODING_INT | long类型整数 |
REDIS_ENCODING_EMBSTR | embstr编码的简单动态字符串 (长度<=32字节) |
REDIS_ENCODING_RAM | 简单动态字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 |
/*默认以RAW字符编码,若需要调整则在调用处显式更改 */
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = REDIS_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
/* Set the LRU to the current lruclock (minutes resolution). */
o->lru = LRU_CLOCK();
return o;
}
/* 创建一个 ZIPLIST 编码的列表对象 */
robj *createZiplistObject(void) {
unsigned char *zl = ziplistNew();
robj *o = createObject(REDIS_LIST,zl);
o->encoding = REDIS_ENCODING_ZIPLIST; //显式更改编码
return o;
}
字符串对象的编码可以是int embstr raw
根据不同的操作,会将值的编码进行转换。
字符串命令和对应编码操作:(具体命令及方式见图8-7字符串命令及操作)
命令 | 意义 |
---|---|
SET | 以对应编码保存值 |
GET | 以字符串形式返回值 |
APPEND | 以字符串形式追加值 |
INCRBYFLOAT | 将值转换成浮点数进行计算保存 |
INCRBY | 对整数进行加法计算保存 |
DECRBY | 对整数进行减法计算保存 |
STRLEN | 返回对应值的字符串长度 |
GETRANGE | 获取对应值的字符串形式,及其索引字符 |
列表对象的编码可以是ziplist linkedlist
redis>RPUSH members 1 "three" 5
(integer)3
1)若为ziplist编码:
|redisObject
|type=REDIS_LIST
|encoding=REDIS_ENCODING_ZIPLIST
|ptr ---->|zlbytes|zltail|zllen|1|"three"|5|zlend|
|...
2)若为linkedlist编码:
双端链表中每个节点是一个StringObject,每个StringObject的编码根据对应的值决定
|redisObject
|type=REDIS_LIST
|encoding=REDIS_ENCODING_LINKEDLIST
|ptr ---->|StringObject:1|StringObject:"three"|StringObject:5|
|...
列表对象采用ziplist编码的条件:(两个都需要满足)
①列表对象保存的所有字符串元素长度都小于64字节
②列表对象保存的元素数量小于512个
若不能满足以上两个条件,则采用linkedlist编码。
列表命令及相应编码操作:(具体见图8-8列表命令及操作)
LPUSH
RPUSH
LPOP
RPOP
LINDEX
LLEN
LINSERT
LREM
LTRIM
LSET
哈希对象编码可以是ziplist hashtable
redis> HSET profile name "tom"
(integer)1
redis> HSET profile age 25
(integer)1
redis> HSET profile career "programmer"
(integer)1
1)ziplist编码:当有新键值对要加入时,
先将保存键的压缩列表节点放置压缩列表表尾,
再将保存值的压缩列表节点放置压缩列表表尾。
因此同一键值对的节点会紧挨在一起,且先添加入的键值对靠前,后添加的键值对靠后。
|redisObject
|type=REDIS_HASH
|encoding=REDIS_ENCODING_ZIPLIST
|ptr ---->|zlbytes|zltail|zllen|"name"|"tom"|"age"|25|"career"|"programmer"|zlend|
|...
2)hashtable编码: 每个键值对使用一个字典键值对来保存
每个键和值都是一个字符串对象。
|redisObject
|type=REDIS_HASH
|encoding=REDIS_ENCODING_HT
|ptr ----> |dict|
|... |StringObject:"age" |-->|StringObject:25|
|StringObject:"career"|-->|StringObject:"programmer"|
|StringObject:"name" |-->|StringObject:"tom"|
哈希对象采用ziplist编码的条件:(两个都需要满足)
①哈希对象保存的所有键值对的键和值 字符串长度都小于64字节
②哈希对象保存的键值对数量小于512个
若不能满足以上两个条件,则采用hashtable编码。
哈希命令及相应编码操作:(具体见图8-9哈希命令及操作)
命令:
HSET
HGET
HEXISTS
HDEL
HLEN
HGETALL
集合对象的编码可以是intset hashtable
1)intset编码:保存在整数集合中。
redis> SADD members 1 3 5
(integer)3
|redisObject |-> intset
|type=REDIS_SET | encoding=INT16
|encoding=REDIS_ENCODING_INTSET | length=3
|ptr --------------------- contents-->|1|3|5|
1)hashtable编码:字典的每个键都是字符串对象,
每个字符串对象包含一个集合元素
字典的值全部设置为null
redis> SADD members "apple" "banana" "cherry"
(integer)3
|redisObject
|type=REDIS_SET
|encoding=REDIS_ENCODING_HT
|ptr ----> |dict|
|... |StringObject:"apple" |-->NULL
|StringObject:"banana"|-->NULL
|StringObject:"cherry"|-->NULL
集合对象采用intset编码的条件:(两个都需要满足)
①集合对象保存的所有元素都是整数值
②集合对象保存的元素数量小于512个
若不能满足以上两个条件,则采用hashtable编码。
集合命令及相应编码操作:(具体见图8-10集合命令及操作)
命令:
SADD
SCARD
SISMEMBER
SMEMBERS
SRANDMEMBER
SPOP
SREM
有序集合对象的编码可以是ziplist skiplist
redis> SADD members "apple" 8.0 "banana" 5.0 "cherry" 9.0
(integer)3
1)ziplist编码: 当有新键值对要加入时,会对分值低的键值对先插入
先将保存键的压缩列表节点放置压缩列表表尾,
再将保存值的压缩列表节点放置压缩列表表尾。
因此同一键值对的节点会紧挨在一起,分值低的元素靠前,分值高的靠后。
|redisObject
|type=REDIS_ZSET
|encoding=REDIS_ENCODING_ZIPLIST
|ptr ---->|zlbytes|zltail|zllen|"banana"|5.0|"apple"|8.0|"cherry"|9.0|zlend|
|...
2)skiplist编码: 使用zset结构作为底层实现,zset结构包含一个字典和跳跃表
typedef struct zset{
zskiplist *zsl;
dict *dict;
}zset;
zset结构中的zsl跳跃表按分值从小到大保存所有集合元素,每个跳跃表节点都保存了一个集合元素
跳跃表节点的object属性保存元素的成员,score属性保存元素分值。
zset结构中dict字典为有序集合创建了一个从成员到分值的映射,字典每个键值对都保存一个集合元素(成员对象和它的分值)
有序集合每个元素的成员都是字符串对象,元素的分值都是double类型浮点数。
(跳跃表和字典都通过指针共享相同元素的成员和分值,不会有重复元素副本)
为什么有序集合zset结构同时使用跳跃表和字典来实现?
答:有序集合需要满足 有序、查找 等功能
单独使用跳跃表实现,能够保证有序及范围型操作功能,但在根据成员查找分值操作时复杂度为O(logN)
单独使用字典实现,能够保证成员和分值映射查找复杂度为O(1) 但不能以有序方式保存集合元素。
skiplist编码的有序集合实现方式,见图8-16skiplist编码有序集合对象
有序集合对象采用ziplist编码的条件:(两个都需要满足)
①有序集合对象保存的所有元素成员长度小于64字节
②有序集合对象保存的元素数量小于128个
若不能满足以上两个条件,则采用skiplist编码。
有序集合命令及相应编码操作:(具体见图8-11有序集合命令及操作)
命令:
ZADD
ZCARD
ZCOUNT
ZRANGE
ZREVRANGE
ZRANK
ZREVRANK
ZREM
ZSCORE