一、redisObject
在redis中基本的结构对象我们称之为RedisObject,其源码如下:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;
其中:
- type:表示值的数据类型。
- encoding:值的编码方式,就是其底层的数据结构实现。
- lru:记录了对象最后一次被访问的时间,用于淘汰过期的键值对。
- refcount:记录了对象的引用计数。
- *ptr:指向数据的指针
二、String的动态字符串
在redis底层中对,string的表示主要有三种结构,分别是int编码格式;embstr编码格式;raw编码格式。
- 当长度小于20,并且是数值类型时,其底层就是用long来进行数据存储;这样做的好处就是可以减少空间占用,使用long的话最多也就占8个字节,使用char类型的话,会占用更多的内存空间。
- 当存储是字符串的数据时,并且字符串的长度小于等于44字节时,以及长度大于22时,RedisObject和SDS会被分配到同一块内存空间,这样可以避免内存碎片化的问题,这种方式称为embstr编码方式。
- 当存储是字符串的数据时,并且字符串的长度大于44字节时,那么RedisObject和SDS不会被分配在同一片内存空间,这种方式称为raw编码方式
其中SDS即Simple Dynamic String,其构造如下图所示:
其中:
- len:占用4个字节,表示buf的已用长度
- alloc:占用4个字节,表示buf实际分配的长度
- buf:字节数组,保存实际的数据,为了表示字节数组的结束,redis默认在最后加一个"\0"多占用1个字节。
三、List
List数据结构:其底层的数据结构实现是 双向链表 + 压缩链表(ziplist),通过将每个压缩表用双向链表的方式连接起来,来节省内存空间。
ziplist是一个经过特殊编码的双向链表,它的设计目标就是为了提高存储效率,节省内存空间。ziplist可以用于存储字符串或整数,其中整数是按真正的二进制表示进行编码的,而不是编码成字符串序列。它能以O(1)的时间复杂度在表的两端提供push和pop操作。
其中:
- zlbytes:表示列表长度
- zltail:表示列表尾的偏移量
- zllen:表示列表中entry的数量
- zlend:用255来表示结束符
每个entry的结构如下:
- prev_len:表示前一个entry的长度;有两种表示方式
- 1字节——取1字节表示前一个entry的长度小于254个字节
- 5字节——其它长度时,取5字节
- encoding:表示编码方式,占用1字节
- len:表示自身长度,占4字节
- content:保存的实际数据
四、hash
Hash:其底层结构主要有两种方式来实现:hash表(dict)和ziplist,其默认实现是ziplist。
hash结构如下图所示:
并且在redis是存在两个hash表(hash1、hash2),一开始数据量不大的时候,我们的数据都会存放在hash1表中,当我们的key的数量在不断增加的时候,触发我们的rehash过程,这个时候hash2的结构是hash1的2倍,那么这个rehash的过程其实就是每一次操作过程中进行的,每次一个请求,都会讲一个哈希桶的数据放在hash2中,这样可以将操作分散在每个操作中,不会出现系统突然的卡顿现象。其流程如下:
rehash的过程:
- 给哈希表2分配更大的空间,例如是原来哈希表1的两倍
- 将哈希表1中的数据重新映射并拷贝到哈希表2中;
- 释放掉哈希表1中的空间
当发生如下两个情况时ziplist会转化成dict;并且这个过程是不可逆的。
- 超过ziplist单个entry的长度限制;默认为64bytes
- 超过ziplist最大entry个数限制:默认是512
五、Set
Set数据结构:其底层主要有整数数组(INTSET)和哈希表两种实现方式。当我们创建set的时候如果遇上成员是整形字符串时,会直接使用intset编码存储。intset的数据结构:
其中:整数数组的一个有序集合,其内部是采用二分查找来定位元素位置。
- encoding表示编码格式:表示intset中每个元素采用以下哪种方式存储,有三种类型INT16(2字节),INT32(4个字节),INT64(8个字节)
- length:元素个数,表示intset保存在contents中的元素个数
- contents:实际存储的数据
每个编码格式的转化如下图所示:
六、Sorted Set
zset数据结构:即有序的set,其底层时间是ziplist和skiplist。
skiplist结构如下图所示:
其中:skiplist的头部元素是不存储数据的,其值是存储在后面的节点中,span表示的就是步长
- header表示头部元素,tail表示尾部元素
- length表示元素个数
- level表示层级
当符合以下条件中的任意一个时使用skiplist结构存储:
- 配置了ziplist的最大元素个数为0,即不使用ziplist
- 配置了ziplist的最大元素个数,并且zset元素大于它
其中zset底层两种结构可以发生相互转化的过程
- ziplist转化成skiplist
- 当ziplist的元素个数大于配置的最大值时,会强制转化成skiplist编码格式(默认128)
- 当要插入ziplist的元素长度大于配置的最大值时,会强制转化成skiplist编码格式(默认64)
- skiplist转换成ziplist
- skiplist中元素长度小于配置的ziplist最大值,并且skiplist中元素的最大长度小于等于配置的ziplist元素最大值