前面结算了底层的数据类型。接下来介绍redis的五大基本类型string、hash、list、set、zset,为了便于操作,Redis定义了redisObjec结构体来表示string、hash、list、set、zset五种数据类型。
typedef struct redisObject {
//ype表示具体的数据类型,也就是string、hash、list、set、zset
unsigned type:4;
//encoding表示该类型的物理编码方式,同一种数据类型可能有不同的编码方式
unsigned encoding:4;
//lru字段表示当内存超限时采用LRU算法清除内存中的对象
unsigned lru:REDIS_LRU_BITS;
//refcount表示对象的引用计数
int refcount;
//ptr指针指向真正的存储结构
void *ptr;
} robj;
以String为例。简单来看一下引用过程
这样理解应该就简单多了。
再来看redisObject的上层对象dictEntity,类似HashMap的entity。Redis给每个key-value键值对分配一个dictEntry,里面有着key和val的指针,next指向下一个dictEntry形成链表,这个指针可以将多个哈希值相同的键值对链接在一起,由此来解决哈希冲突问题(链地址法)。
这样设计的好处是,可以针对不同的使用场景,对5中常用类型设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。无论是dictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。比如jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。通过数据的类型和大小来选择合适的底层数据类型来和合适的编码格式以及内存大小存储。
下面来看五个基本类型的底层类型选择
1. String
字符串对象的底层实现可以是整数集合、raw(sds)、embstr编码的sds。embstr编码是通过调用一次内存分配函数来分配一块连续的空间,而sds需要调用两次。
int编码字符串对象和embstr编码字符串对象在一定条件下会转化为raw编码字符串对象。embstr:<=39字节的字符串。int:8个字节的长整型。raw:大于39个字节的字符串
int类型如果添加了字符串会直接转为 raw也就是sds
互联网主要用于存储:缓存、限流、计数器、分布式锁、分布式Session
2. List
List对象的底层实现是quicklist(快速列表,是ziplist 压缩列表 和linkedlist 双端链表 的组合)。Redis中的列表支持两端插入和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。
老版本是根据数据的长度来分别使用ziplist和linkedlist存储的
新版本使用的是quickList
quickList 是 zipList 和 linkedList 的混合体。它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。因为链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率。
quicklist 默认的压缩深度是 0,也就是不压缩。为了支持快速的 push/pop 操作,quicklist 的首尾两个 ziplist 不压缩,此时深度就是 1。为了进一步节约空间,Redis 还会对 ziplist 进行压缩存储,使用 LZF 算法压缩。
3. hash
Hash对象的底层实现可以是ziplist(压缩列表)或者hashtable(字典或者也叫哈希表)
Hash对象只有同时满足下面两个条件时,才会使用ziplist(压缩列表):1.哈希中元素数量小于512个;2.哈希中所有键值对的键和值字符串长度都小于64字节。
4. Set
Set集合对象的底层实现可以是intset(整数集合)或者hashtable(字典或者也叫哈希表)。
intset(整数集合)当一个集合只含有整数,并且元素不多时会使用intset(整数集合)作为Set集合对象的底层实现。
5. ZSet
Set有序集合对象底层实现可以是ziplist(压缩列表)或者skiplist(跳跃表)。当一个有序集合的元素数量比较多或者成员是比较长的字符串时,Redis就使用skiplist(跳跃表)作为ZSet对象的底层实现。