目录
- RedisObject
- Redis的数据类型及底层实现
- String
- (1)int编码
- (2)row编码
- (3)embstr编码
- Hash
- (1)ziplist编码
- (2)hashtable编码
- List
- (1)ziplist编码
- (2)linkedlist编码
- Set 集合
- (1)intset编码
- (2)hashtable编码
- Zset 有序集合
- (1) skiplist 跳跃表
- (2)ziplist 压缩列表
RedisObject
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 指向底层实现数据结构的指针
void *ptr;
// ...
} robj;
redis是以键值对存储数据的,所以对象又分为键对象和值对象,即存储一个key-value键值对会创建两个对象,键对象和值对象。
- 键对象总是一个字符串对象,而值对象可以是五大对象中的任意一种。
type属性存储的是对象的类型,也就是我们说的 string、list、hash、set、zset中的一种,可以使用命令 TYPE key 来查看。 - encoding属性记录了队形所使用的编码,即这个对象底层使用哪种数据结构实现。
Redis的数据类型及底层实现
String
redis最基础的数据类型,为简单key-value结构,并且value不限制类型,可以是数字,字符,图片等序列化对象,最大不可超过512M。
string类型在使用中经常用作分布式session共享,计数器,验证码过期,基础数据的缓存等
string底层有三种编码方式
(1)int编码
字符串保存的是整数值,并且这个正式可以用long类型来表示,那么其就会直接保存在redisObject的ptr属性里,并将编码设置为int
(2)row编码
字符串保存的大于32字节的字符串值,则使用简单动态字符串(SDS)结构,并将编码设置为raw,此时内存结构与SDS结构一致,内存分配次数为两次,创建redisObject对象和sdshdr结构,
(3)embstr编码
字符串保存的小于等于32字节的字符串值,使用的也是简单的动态字符串(SDS结构),但是内存结构做了优化,用于保存顿消的字符串;内存分配也只需要一次就可完成,分配一块连续的空间即可
SDS结构体定义如下:
注:
free属性的值为0,表示这个SDS没有分配任何未使用空间。
len属性的值为5,表示这个SDS保存了一个五字节长的字符串。
buf属性是一个char类型的数组,数组最后保存了空字符‘\0’。
SDS和c字符串的区别
- SDS获取字符串长度复杂度为常数
SDS通过获取len属性就可以得到字符串的长度,时间复杂度为:O(1)O(1)O(1)
c字符串需要遍历字符串,时间复杂度为:O(N)O(N)O(N) - SDS杜绝了缓冲区溢出
c字符串如果没有重新分配空间,直接修改字符串的话,可能会造成数据溢出。
当SDS的API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的需求,如果不满足,则自动将SDS空间扩展至所需大小。 - 减少内存重分配次数
SDS通过空间预分配和惰性空间释放两种优化策略来减少内存重分配次数。
- 空间预分配
Redis通过额外分配未使用的空间,优化了SDS的字符串增长操作,减少了连续执行字符串增长操作所需的内存分配次数。 - 惰性空间释放
惰性空间释放用于优化SDS的字符串缩短操作。当SDS缩短时,程序并不会立即回收缩短后多出来的空间,而是使用free属性将这些字节的数量记录起来,等待将来使用
注:如果需要真正地释放SDS的未使用空间,我们可以使用相应的API。
- 二进制安全
SDS API会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何的限制、过滤或者假设,所以SDS的API都是二进制安全的。 - SDS兼容部分c字符串函数
SDS之所以在末尾保存一个空字符’\0’,是为了使用一些c字符串<string.h>函数库,避免不必要的代码重复。
例如 字符串对比函数:<string.h>/strcasecmp函数
字符串对象总结:
- 在Redis中,存储long、double类型的浮点数是先转换为字符串再进行存储的。
- raw与embstr编码效果是相同的,不同在于内存分配与释放,raw两次,embstr一次。
- embstr内存块连续,能更好的利用缓存在来的优势
- int编码和embstr编码如果做追加字符串等操作,满足条件下会被转换为raw编码;embstr编码的对象是只读的,一旦修改会先转码到raw。
Hash
hash表数据类型是key-value结构,但是key为String类型,而value的结构则是field-value结构;类似于java中的Map<Sring,Map<Stirng,Object>> 结构。hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
常用命令 存:HSET key field value ,取:HGET key field
哈希对象的编码可以是ziplist和hashtable之一。
(1)ziplist编码
在ziplist编码的哈希对象中,key-value键值对是以紧密相连的方式放入压缩链表的,先把key放入表尾,再放入value;键值对总是向表尾添加。
哈希对象使用ziplist编码需要满足两个条件:一是所有键值对的键和值的字符串长度都小于64字节;二是键值对数量小于512个;不满足任意一个都使用hashtable编码。
以上两个条件可以在Reids配置文件中修改hash-max-ziplist-value选项和hash-max-ziplist-entries选项。
(2)hashtable编码
hashtable编码的哈希对象底层实现是字典,哈希对象中的每个key-value对都使用一个字典键值对来保存。
字典键值对即是,字典的键和值都是字符串对象,字典的键保存key-value的key,字典的值保存key-value的value。
List
列表类型,类似一个队列,左侧为表头,右侧为表尾,左右两侧都可以插入数据。
list数据类型可以作为消息队列使用,还可以作为排名等缓存,也可以作为缓存分页。
列表对象的编码可以是ziplist和linkedlist之一。
(1)ziplist编码
列表对象使用ziplist编码需要满足两个条件:一是所有字符串长度都小于64字节,二是元素数量小于512,不满足任意一个都会使用linkedlist编码。
两个条件的数字可以在Redis的配置文件中修改,list-max-ziplist-value选项和list-max-ziplist-entries选项。
(2)linkedlist编码
linkedlist编码底层采用双端链表实现,每个双端链表节点都保存了一个字符串对象,在每个字符串对象内保存了一个列表元素。
Set 集合
set数据类型为无序集合数据类型,且数据不重复
常用于存储一些集合类型的数据,如微博的所有粉丝,标签等数据;结构类似java中的HashSet。
集合对象的编码可以是intset和hashtable之一。
(1)intset编码
intset编码的集合对象底层实现是整数集合,所有元素都保存在整数集合中。
集合对象使用intset编码需要满足两个条件:一是所有元素都是整数值;二是元素个数小于等于512个;不满足任意一条都将使用hashtable编码。可以在Redis配置文件中修改set-max-intset-entries选项。
(2)hashtable编码
字典实现,同Hash
Zset 有序集合
有序集合类型,且集合内元素不重复;
Zset底层实现有两种方式
(1) skiplist 跳跃表
skiplist编码的有序集合对象底层实现是跳跃表和字典两种;
每个跳跃表节点都保存一个集合元素,并按分值从小到大排列;节点的object属性保存了元素的成员,score属性保存分值;
字典的每个键值对保存一个集合元素,字典的键保存元素的成员,字典的值保存分值。
为何skiplist编码要同时使用跳跃表和字典实现?
- 跳跃表优点是有序,但是查询分值复杂度为O(logn);字典查询分值复杂度为O(1) ,但是无序,所以结合连个结构的有点进行实现。
- 虽然采用两个结构但是集合的元素成员和分值是共享的,两种结构通过指针指向同一地址,不会浪费内存。
(2)ziplist 压缩列表
其结构与哈希对象类似,不同的是两个紧密相连的压缩列表节点,第一个保存元素的成员,第二个保存元素的分值,而且分值小的靠近表头,大的靠近表尾。
使用ziplist编码需要满足两个条件:一是所有元素长度小于64字节;二是元素个数小于128个;不满足任意一条件将使用skiplist编码。以上两个条件可以在Redis配置文件中修改zset-max-ziplist-entries选项和zset-max-ziplist-value选项。