上一章讲解了Redis中的数据结构,这一章对Redis中的对象做简要介绍。本篇文章是对Redis对象做一个全局性的概览,每个对象的代码实现部分以后会一一解析。
Redis对象
Redis中有五大对象,分别是:字符串对象,列表对象,哈希对象,集合对象,有序集合对象。每一个对象都需要存储键值对,而key必须是字符串对象,value可能是五种类型对象之一。所以了解对象底层的存储编码方式有助于加深对Redis底层的了解,同时也是Redis设计思想中的重中之重,属于了解Redis的第一步。
Redis中的每个对象都是redisObject结构体表示的。
// 对象类型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
// 对象编码
#define REDIS_ENCODING_RAW 0
#define REDIS_ENCODING_INT 1
#define REDIS_ENCODING_HT 2
#define REDIS_ENCODING_ZIPMAP 3 //后面的版本已经摒弃,这里不再考虑
#define REDIS_ENCODING_LINKEDLIST 4
#define REDIS_ENCODING_ZIPLIST 5
#define REDIS_ENCODING_INTSET 6
#define REDIS_ENCODING_SKIPLIST 7
#define REDIS_ENCODING_EMBSTR 8
//redis对象结构体
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;
字符串对象
字符串对象底层实现可能编码有三种,分别是raw SDS、整数、和embstr编码的SDS。当字符串对象保存的是一个整数值,则可以直接用整型编码来表示,即ptr指向一个long;当字符串对象保存的是一个字符串且字符串值的长度大于39字节,则用raw编码方式保存这个字符串的值,即ptr指向一个SDS类型;当字符串对象保存的是一个字符串且字符串值的长度小于39字节,则用embstr编码方式保存字符串。
int编码和raw编码都很简单,这里主要详细说一下embstr编码。embstr编码主要适合于短字符串的编码实现,通过调用一次内存分配函数分配一个连续的内存空间,用来存放redisobject和sdshdr对象。相比于raw编码方式,其有三个优点:
- 内存分配时候从raw编码的两次(分别申请redisobject和sdshdr)变成一次。
- 内存释放时候从raw编码的两次(分别释放redisobject和sdshdr)变成一次。
- embstr编码中字符串对象是在连续的内存中,有利于缓存。
还有一点需要注意,字符串对象的三种编码方式之间可以转换。关系如下图。int编码当时当用append命令加上字符串的时候就变成raw编码方式了,embstr编码方式也是只读方式,当尝试对其做任何修改,也会将其转换成raw编码方式。
字符串对象常用的命令如下:
命令 | 作用 |
---|---|
SET | 插入字符串对象的值 |
GET | 根据键返回字符串值 |
APPEND | 根据键将字符串插入原值后面 |
INCRBY | 将整数值加法(只有整型编码才可以执行) |
DECRBY | 将整数值减法(只有整形编码才可以执行) |
STRLEN | 返回字符串的长度 |
SETRANGE | 将索引上的值设定为给定的字符(只有raw编码可以执行) |
GETRANGE | 返回字符串索引上的值(只有raw编码可以执行) |
列表对象
列表对象的底层编码是linkedlist和ziplist。当列表对象保存的元素数小于512且字符串元素小于64字节的时候用ziplist编码,不满足的时候用linkedlist编码。两种编码可以转换。下图是两种编码类型的图示。
列表常用的命令如下:
命令 | 作用 |
---|---|
LPUSH | 列表中头部插入值 |
RPUSH | 列表尾部插入值 |
LPOP | 列表头部弹出值 |
RPOP | 列表尾部弹出值 |
LLEN | 返回列表的长度 |
LINDEX | 返回列表索引的值 |
LINSERT | 在列表指定索引的位置中插入值 |
LSET | 在列表指定索引位置更新节点的值 |
LREM | 在列表中删除给定值的节点 |
LTRIM | 在列表上删除指定索引之外的节点 |
哈希对象
哈希对象的底层编码有两种ziplist和hashtable。当哈希对象保存的键值对数小于512且键和值字符串长度小于64字节的时候用ziplist编码,不满足的时候用hashtable编码。两种编码可以转换。
命令 | 作用 |
---|---|
HSET | 将新节点添加到字典中 |
HGET | 根据给定键返回相对应的值 |
HEXITS | 根据给定键判断是否字典中存在 |
HDEL | 根据给定键删除字典中的键值对 |
HLEN | 字典中键值对的数量 |
HGETALL | 返回字典中所有的键值对 |
集合对象
集合对象的编码方式也有两种:intset和hashtable。当集合中所有对象都是整数并且元素数量不超过512个就为intset编码,否则用hashtable编码。
命令 | 作用 |
---|---|
SADD | 添加新元素 |
SCARD | 返回集合中的元素数量 |
SISMEMBER | 集合中查找是否存在元素 |
SMEMBERS | 返回整个集合元素 |
SRANDMEMBER | 集合中随机返回一个元素 |
SPOP | 集合中随机删除一个元素 |
SREM | 集合中删除所有的元素 |
有序集合对象
有序集合对象的底层编码是ziplist或skiplist。当有序集合中元素数量小于128且所有元素长度小于64字节时候用ziplist编码,否则用skiplist编码。当使用ziplist编码时,压缩列表中对象按照分值从小到大排列,每个对象先存放对象内容,再存放对象分值。当使用skiplist编码时,其实是同时使用了字典和跳表。字典中存储了对象的元素和分值匹配关系,跳表根据分值从小到大有序存储。
这样设计的主要原因是同时利用跳表和字典的优势,比如ZSCORE命令可以返回指定元素的分值,如果只有跳表则查询的复杂度为O(logn),但是加上字典后查找的复杂度变成O(1)。但是如ZRANGE这类根据范围进行查找的命令就可以有效利用,
命令 | 作用 |
---|---|
ZADD | 插入新元素 |
ZCARD | 返回有序集合中的元素数量 |
ZCOUNT | 有序集合给定分值范围内的元素数量 |
ZRANGE | 从表头到表尾遍历,返回索引范围内的元素数量 |
ZREVRANGE | 从表尾到表头遍历,返回索引范围内的元素数量 |
ZSCORE | 有序集合中返回给定成员的分值 |
ZREM | 有序集合中删除所有包含给定成员的元素 |
常用作用于所有对象的命令
命令 | 作用 |
---|---|
TYPE | 查看键值对中值对象的类型 |
OBJECT ENCODING | 查看键值对中值对象的编码类型 |
KEY + 模式 | 查找数据库中满足模式的KEY |
参考
- 《Redis设计与实现》
- Redis命令