0%

Redis对象的9种类型

Redis中基本数据结构有:简单动态字符串SDS、双端链表、跳表、字典、整数集合、压缩列表。但是Redis并没有直接使用这些数据结构来存放键值对,而是在这些基本数据结构之上构建了Redis的对象来表示。Redis对象系统包含:字符串对象String、列表对象List、哈希对象Hash、集合对象Set、有序集合对象ZSet、bitmaps、HyperLogLogs、Streams、GeoHash。

使用对象而不使用基本的数据结构来直接表示的好处是:

  • Redis可以为不同场景下使用的对象来设置不同的数据结构。
  • Redis可以根据对象的类型来判断一个对象是否可以执行指定的命令。
  • Redis对象系统实现了基于引用计数的内存回收机制。
  • Redis对象中还记录了关于键的其他信息,比如访问时间,用来实现键过期策略以及内存淘汰机制。

Redis对象的的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
redisObject {
// 类型
unsigned type:4;

// 编码
unsigned encoding:4;

// 指向底层实现数据结构的指针
void * ptr;

// lru,记录对象最后一次被访问的时间
unsigned lru:22;

// 引用计数
int refcount;

// ...
}

Redis中基本数据结构有:简单动态字符串SDS、双端链表、跳表、字典、整数集合、压缩列表。但是Redis并没有直接使用这些数据结构来存放键值对,而是在这些基本数据结构之上构建了Redis的对象来表示。Redis对象系统包含:字符串对象String、列表对象List、哈希对象Hash、集合对象Set、有序集合对象ZSet、bitmaps、HyperLogLogs、Streams、GeoHash。

使用对象而不使用基本的数据结构来直接表示的好处是:

  • Redis可以为不同场景下使用的对象来设置不同的数据结构。
  • Redis可以根据对象的类型来判断一个对象是否可以执行指定的命令。
  • Redis对象系统实现了基于引用计数的内存回收机制。
  • Redis对象中还记录了关于键的其他信息,比如访问时间,用来实现键过期策略以及内存淘汰机制。

Redis对象的的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
redisObject {
// 类型
unsigned type:4;

// 编码
unsigned encoding:4;

// 指向底层实现数据结构的指针
void * ptr;

// lru,记录对象最后一次被访问的时间
unsigned lru:22;

// 引用计数
int refcount;

// ...
}

redisObject-1

字符串对象

字符串对象的编码可以是:int、raw、embstr:

  • 如果一个字符串对象保存的是整数值,并且整数值可以用long来表示,这个字符串编码是int。
  • 如果一个字符串对象保存的是字符串值,并且字符串长度大于32字节,将使用SDS简单动态字符串来表示,对象编码是raw。
  • 如果一个字符串对象保存的是字符串值,并且字符串长度小于32字节,将使用embstr编码的方式来保存这个字符串。
  • long double类型表示的浮点数在Redis中也是作为字符串来保存的。

embstr

embstr是用来保存短字符串的一种优化编码方式,和raw编码一样使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配来分别创建redisObject和sdshdr,而embstr会一次内存分配来分配一块连续空间,依次包含redisObject和sdshdr。

编码的转换

int编码和embstr编码的字符串在一定条件下会转换为raw编码的字符串对象:

  • int编码的字符串值修改为不是整数的字符串值,就变成了raw编码的字符串对象。
  • embstr编码的字符串对象实际上是只读的,如果修改embstr字符串的值,会成一个raw编码的字符串对象,然后再修改,最后就变成了一个raw编码的字符串对象。

列表对象

列表对象的编码可以是:ziplist、linkedlist:

  • ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存一个列表元素。
  • linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,每个字符串对象都保存了一个列表元素。

编码的转换

当列表对象保存的所有的字符串元素长度都小于64字节并且列表保存的元素数量小于512个,列表对象使用ziplist编码,否则使用linkedlist编码。

哈希对象

哈希对象的编码可以是:ziplist、hashtable:

  • ziplist编码的哈希对象使用压缩列表作为底层实现。当要加入哈希对象时,会先将保存了键的压缩列表节点推入压缩列表表尾,然后再将保存了值的压缩列表节点推入压缩列表表尾。保存了同一键值对的两个节点总是挨在一起。
  • hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键都是一个字符串对象,对象中保存了键;哈希对象中每个值都是一个字符串对象,对象中保存了值。

编码的转换

当哈希对象保存的所有键值对的键值的字符串长度都小于64字节,并且键值对数量小于512个,就使用ziplist编码,否则使用hashtable编码。

集合对象

集合对象编码可以是:intset、hashtable:

  • intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都保存在整数集合里。
  • hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象都包含了一个集合元素,字典的值全部设置为NULL。

编码的转换

集合对象保存的都是整数值并且元素数量不超过512个,则使用intset编码,否则使用hashtable编码。

有序集合对象

有序集合对象的编码可以是:ziplist、skiplist:

  • ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点表示,第一个节点保存元素的成员member,第二个节点保存元素的分值score。压缩列表内容按分值从小到大排序。
  • skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset同时包含一个字典和一个跳表,跳表按照分值从小到大保存了所有集合元素,每个跳表节点的object属性保存了元素成员,跳表的score属性保存了元素的分值,通过跳表可对有序集合进行范围操作。zset的字典为有序集合创建了一个从成员到分值的映射,字典中每个键值对都保存了一个集合元素,键保存了元素的成员,值保存了元素的分值,通过字典可以使用O(1)复杂度查找成员的分值。
  • 有序集合的每个元素的成员都是一个字符串对象,每个元素分值都是一个double类型的浮点数。

编码的转换

当有序集合对象保存的元素数量小于128并且所有元素成员的长度都小于64字节,则使用ziplist编码,否则使用skiplist编码。

bitmap

bitmap不是一种真实的数据结构,本质上是String数据结构。

GeoHash

GeoHash本身也不是一种数据结构,而是借助ZSet实现的,将将维度使用52位整数进行编码,放进zset中,score是GeoHash的52位整数值。

Geo查询内部使用zset查询,通过score排序就可得到附近的坐标,将score还原成经纬度就可以得到结果。

处理的过程:

  1. 将二维坐标转换为一维整数编码值
  2. 使用zset存储,score就是整数编码值
  3. 使用zrangbyrank获取score相近的元素zrangebyscore
  4. 通过score还原成坐标

HyperLogLog

Redis高级数据结构,用来统计基数。

Steams

Redis5.0引入的全新数据结构,是一个内存版的kafka,有Consumer Groups的概念,Streams底层数据结构是radix tree。

字符串对象

字符串对象的编码可以是:int、raw、embstr:

  • 如果一个字符串对象保存的是整数值,并且整数值可以用long来表示,这个字符串编码是int。
  • 如果一个字符串对象保存的是字符串值,并且字符串长度大于32字节,将使用SDS简单动态字符串来表示,对象编码是raw。
  • 如果一个字符串对象保存的是字符串值,并且字符串长度小于32字节,将使用embstr编码的方式来保存这个字符串。
  • long double类型表示的浮点数在Redis中也是作为字符串来保存的。

embstr

embstr是用来保存短字符串的一种优化编码方式,和raw编码一样使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配来分别创建redisObject和sdshdr,而embstr会一次内存分配来分配一块连续空间,依次包含redisObject和sdshdr。

编码的转换

int编码和embstr编码的字符串在一定条件下会转换为raw编码的字符串对象:

  • int编码的字符串值修改为不是整数的字符串值,就变成了raw编码的字符串对象。
  • embstr编码的字符串对象实际上是只读的,如果修改embstr字符串的值,会成一个raw编码的字符串对象,然后再修改,最后就变成了一个raw编码的字符串对象。

列表对象

列表对象的编码可以是:ziplist、linkedlist:

  • ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存一个列表元素。
  • linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,每个字符串对象都保存了一个列表元素。

编码的转换

当列表对象保存的所有的字符串元素长度都小于64字节并且列表保存的元素数量小于512个,列表对象使用ziplist编码,否则使用linkedlist编码。

哈希对象

哈希对象的编码可以是:ziplist、hashtable:

  • ziplist编码的哈希对象使用压缩列表作为底层实现。当要加入哈希对象时,会先将保存了键的压缩列表节点推入压缩列表表尾,然后再将保存了值的压缩列表节点推入压缩列表表尾。保存了同一键值对的两个节点总是挨在一起。
  • hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键都是一个字符串对象,对象中保存了键;哈希对象中每个值都是一个字符串对象,对象中保存了值。

编码的转换

当哈希对象保存的所有键值对的键值的字符串长度都小于64字节,并且键值对数量小于512个,就使用ziplist编码,否则使用hashtable编码。

集合对象

集合对象编码可以是:intset、hashtable:

  • intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都保存在整数集合里。
  • hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象都包含了一个集合元素,字典的值全部设置为NULL。

编码的转换

集合对象保存的都是整数值并且元素数量不超过512个,则使用intset编码,否则使用hashtable编码。

有序集合对象

有序集合对象的编码可以是:ziplist、skiplist:

  • ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点表示,第一个节点保存元素的成员member,第二个节点保存元素的分值score。压缩列表内容按分值从小到大排序。
  • skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset同时包含一个字典和一个跳表,跳表按照分值从小到大保存了所有集合元素,每个跳表节点的object属性保存了元素成员,跳表的score属性保存了元素的分值,通过跳表可对有序集合进行范围操作。zset的字典为有序集合创建了一个从成员到分值的映射,字典中每个键值对都保存了一个集合元素,键保存了元素的成员,值保存了元素的分值,通过字典可以使用O(1)复杂度查找成员的分值。
  • 有序集合的每个元素的成员都是一个字符串对象,每个元素分值都是一个double类型的浮点数。

编码的转换

当有序集合对象保存的元素数量小于128并且所有元素成员的长度都小于64字节,则使用ziplist编码,否则使用skiplist编码。

bitmap

bitmap不是一种真实的数据结构,本质上是String数据结构。

GeoHash

GeoHash本身也不是一种数据结构,而是借助ZSet实现的,将将维度使用52位整数进行编码,放进zset中,score是GeoHash的52位整数值。

Geo查询内部使用zset查询,通过score排序就可得到附近的坐标,将score还原成经纬度就可以得到结果。

处理的过程:

  1. 将二维坐标转换为一维整数编码值
  2. 使用zset存储,score就是整数编码值
  3. 使用zrangbyrank获取score相近的元素zrangebyscore
  4. 通过score还原成坐标

HyperLogLog

Redis高级数据结构,用来统计基数。

Steams

Redis5.0引入的全新数据结构,是一个内存版的kafka,有Consumer Groups的概念,Streams底层数据结构是radix tree。

坚持原创技术分享,您的支持将鼓励我继续创作!
Fork me on GitHub