整体说明
Redis的每一个键值都是使用一个redisObject结构体保存的:
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字段表示数据类型:
/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
encoding表示内部编码方式:
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
数据结构与编码方式对应:
字符串类型
redis使用sdshdr类型的变量来存储字符串,redisObject的ptr字段指向sdshdr的地址,sdshdr的定义如下:
struct sdshdr {
int len; //buf已占用的空间长度
int free; //buf中剩余的空间长度
char but[]; //数据 真实存储c字符串
}
比如执行set key "hello"时,内存结构如下:
当存储字符串时,实际占用空间就如上图所示,字符串值存储在sdshdr结构中。
如果值的内容可以用一个64位符号整数表示,redis将会把值转换成long,内存结构如下:
可以看出如果值是long,那么将不会放到sdshdr结构中。
另外,redis会事先建立10000个key,分别存储0到9999这些数字,如果值是0-9999,那么可以直接使用这10000个数字而不需在创建redisObject了。
当配置了maxmemory设置了redis的最大空间大小时,redis不会使用共享对象,因为对于每一个键值都要上会用一个redisObject来记录LRU信息。
当键值内容不超过39个字节时,reids会采用EMBSTR编码,这个编码的好处是把sdshdr结构与redisObject分配到连续的内存空间:
这么做的好处是不管分配存储还是释放内存,所需要的操作都从2次减少到1次。
当对EMBSTR编码的字符串执行修改操作时(如APPEND操作),Redis会将其转换为RAW编码。
散列类型
散列类型内部使用HT或者ZIPLIST编码。
两种数据结构的切换配置在:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
当散列类型字段个数少于hash-max-ziplist-entries个,或者每个字段名和字段值的长度都小于hash-max-ziplist-value个字节,Redis会用ziplist编码存储数据,否则就使用ht存储数据,转换过程是透明的。
Redis的ZIPLIST,是一种紧凑的编码格式,它牺牲了部分读取性能换区极高的空间利用率,所以当entries在较少的个数并且value在较小的长度时使用。
具体实现不在此记录了,可以搜索相关文章。
列表类型
列表类型采用LINKEDLIST和ZIPLIST两种编码。
LINKEDLIST采用双向链表,链表中的元素都是redisObject存储的,此处与字符串优化方式相同。
两种数据结构的切换配置在:
list-max-ziplist-entries 512
list-max-ziplist-value 64
转换方式和Hash一样。
其实linkedlist已经趋向于淘汰了,新版本的redis提供了QUICKLIST编码方式:
将长列表分成若干个以链表形式组织的ziplist,达到减少空间占用的同时还可以提升ziplist编码性能的效果,具体实现可以在网上搜索,此处不记录了。
集合类型
内部实现方式是HT和INTSET。
当元素都是整型且元素的个数小于配置文件中set-max-intset-entries=512个时,Redis会使用INTSET,否则使用HT。
typedef struct intset {
uint32_t encoding; // 编码类型 int16_t、int32_t、int64_t
uint32_t length; // 长度 最大长度:2^32
int8_t contents[]; // 柔性数组
} intset;
其中centents存储的就是集合中的元素,根据encoding不同,每个元素占用的大小也不同。默认是INT16(2字节),当新增加的整数无法用2字节存储时,Redis会将该集合的encoding升级到INT32(4字节),以此类推到INT64(8字节)。INTSET以有序的方式存储元素,所以可用二分法查找,但添加或删除元素都需要调整元素的内存位置,所以元素太多时性能不好,故定义了entries的个数限制。
INTSET转成HT后,不会在自动转回INTSET。
有序集合
内部实现方式是SKIPLIST和ZIPLIST。
两种数据结构的切换配置在:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
当使用ZIPLIST时,
每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。
当使用SKIPLIST时,有序集合对象使用 zet 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表。
关于SKIPLIST,可以在网上搜索redis zset skiplist数据结构,在此处不做详细记录了。
来源:oschina
链接:https://my.oschina.net/u/2450666/blog/3158800