动态字符串 SDS
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

 
String是最基本的 key-value 结构,key 是唯一标识,value 是具体的值,value其实不仅是字符串, 也可以是数字(整数或浮点数),value 最多可以容纳的数据长度是 512M
notion image
 
Redis使用标准C语言编写,但在存储字符时,没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将SDS用作 Redis 的默认字符串表示。
Redis里面, C 字符串只会作为字符串字面量, 用在一些无须对字符串值进行修改的地方, 比如打印日志:
Redis需要的不仅仅是一个字符串字面量, 而是一个可以被修改的字符串值时, Redis就会使用 SDS 来表示字符串值: 比如在Redis的数据库里面, 包含字符串值的键值对在底层都是由 SDS 实现的。
链表 linkedlist
type
status
date
slug
summary
tags
category
icon
password
Property

 
链表提供了高效的节点重排能力, 以及顺序性的节点访问方式, 并且可以通过增删节点来灵活地调整链表的长度。因为Redis使用的C语言并没有内置这种数据结构, 所以Redis构建了自己的链表实现。
链表在Redis中的应用非常广泛, 比如列表键的底层实现之一就是链表: 当一个列表键包含了数量比较多的元素, 又或者列表中包含的元素都是比较长的字符串时, Redis就会使用链表作为列表键的底层实现。
 

队列和栈实现

Redis列表可以被当做栈、队列来使用,如果列表的元素是“右进左出”那就是队列模型;如果元素是“右进右出”那就是栈模型:
压缩列表 ziplist
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

 
压缩列表是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项, 并且每个列表项要么就是小整数值, 要么就是长度比较短的字符串, 那么Redis就会使用压缩列表来做列表键的底层实现。
压缩列表是Redis为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构,占用一块连续的内存空间,不仅可以利用 CPU 缓存,而且会针对不同长度的数据,进行相应编码,这种方法可以有效地节省内存开销。
但是,压缩列表的缺陷也是有的:
  • 不能保存过多的元素,否则查询效率就会降低;
  • 新增或修改某个元素时,压缩列表占用的内存空间需要重新分配,甚至可能引发连锁更新的问题。
因此,Redis 对象(List 对象、Hash 对象、Zset 对象)包含的元素数量较少,或者元素值不大的情况才会使用压缩列表作为底层数据结构。
 
字典
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

 
字典, 又称符号表(symbol table)、关联数组(associative array)或者映射(map), 是一种用于保存键值对(key-value pair)的抽象数据结构。
在字典中, 一个键(key)可以和一个值(value)进行关联(或者说将键映射为值), 这些关联的键和值就被称为键值对。字典中的每个键都是独一无二的, 程序可以在字典中根据键查找与之关联的值, 或者通过键来更新值, 又或者根据键来删除整个键值对, 等等。
字典经常作为一种数据结构内置在很多高级编程语言里面, 但 Redis 所使用的 C 语言并没有内置这种数据结构, 因此 Redis 构建了自己的字典实现。字典在 Redis 中的应用相当广泛, 比如 Redis 的数据库就是使用字典来作为底层实现的, 对数据库的增、删、查、改操作也是构建在对字典的操作之上的。
举个例子, 当执行命令:
在数据库中创建一个键为 "msg" , 值为 "hello world" 的键值对时, 这个键值对就是保存在代表数据库的字典里面的。
除了用来表示数据库之外, 字典还是哈希键的底层实现之一: 当一个哈希键包含的键值对比较多, 又或者键值对中的元素都是比较长的字符串时, Redis 就会使用字典作为哈希键的底层实现。
整数集合 intset
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

 
整数集合是集合键的底层实现之一: 当一个集合只包含整数值元素, 并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。
例如, 如果创建一个只包含五个元素的集合键, 并且集合中的所有元素都是整数值, 那么这个集合键的底层实现就会是整数集合:
 
整数集合是Redis用于保存整数值的集合抽象数据结构, 它可以保存类型为int16_t、 int32_t或者int64_t的整数值, 并且保证集合中不会出现重复元素。每个intset.h/intset结构表示一个整数集合:
contents数组是整数集合的底层实现: 整数集合的每个元素都是contents数组的一个数组项(item), 各个项在数组中按值的大小从小到大有序地排列, 并且数组中不包含任何重复项。length属性记录了整数集合包含的元素数量, 也即是contents数组的长度。
跳跃表 skiplist
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

 
跳跃表(skiplist)是一种有序数据结构, 它通过在每个节点中维持多个指向其他节点的指针, 从而达到快速访问节点的目的。
跳跃表支持平均 、最坏  复杂度的节点查找, 还可以通过顺序性操作来批量处理节点。在大部分情况下, 跳跃表的效率可以和平衡树相媲美, 并且因为跳跃表的实现比平衡树要来得更为简单, 所以有不少程序都使用跳跃表来代替平衡树。
Redis使用跳跃表作为有序集合键的底层实现之一: 如果一个有序集合包含的元素数量比较多, 又或者有序集合中元素的成员是比较长的字符串时, Redis就会使用跳跃表来作为有序集合键的底层实现。
例如, fruit-price 是一个有序集合键, 这个有序集合以水果名为成员, 水果价钱为分值, 保存了 130 款水果的价钱:
fruit-price有序集合的所有数据都保存在一个跳跃表里面, 其中每个跳跃表节点都保存了一款水果的价钱信息, 所有水果按价钱的高低从低到高在跳跃表里面排序:
  • 跳跃表的第一个元素的成员为"banana" , 它的分值为5 
quicklist和listpack
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

quicklist

在 Redis 3.0 之前,List 对象的底层数据结构是双向链表或者压缩列表。然后在 Redis 3.2 的时候,List 对象的底层改由 quicklist 数据结构实现。
其实 quicklist 就是「双向链表 + 压缩列表」组合,因为一个 quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表。虽然压缩列表是通过紧凑型的内存布局节省了内存开销,但是因为它的结构设计,如果保存的元素数量增加,或者元素变大了,压缩列表会有「连锁更新」的风险,一旦发生,会造成性能下降。
quicklist解决办法,通过控制每个链表节点中的压缩列表的大小或者元素个数,来规避连锁更新的问题。因为压缩列表元素越少或越小,连锁更新带来的影响就越小,从而提供了更好的访问性能。
 
quicklist的结构体跟链表的结构体类似,都包含了表头和表尾,区别在于quicklist的节点是quicklistNode
对象
type
status
date
slug
summary
tags
category
icon
password
Property
 
Redis的数据结构有简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合, 等等。Redis并没有直接使用这些数据结构来实现键值对数据库, 而是基于这些数据结构创建了一个对象系统, 这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象。
通过这五种不同类型的对象,Redis可以在执行命令之前, 根据对象的类型来判断一个对象是否可以执行给定的命令。 使用对象的另一个好处是,可以针对不同的使用场景, 为对象设置多种不同的数据结构实现, 从而优化对象在不同场景下的使用效率。
除此之外, Redis的对象系统还实现了基于引用计数技术的内存回收机制: 当程序不再使用某个对象的时候, 这个对象所占用的内存就会被自动释放; 另外, Redis还通过引用计数技术实现了对象共享机制, 这一机制可以在适当的条件下, 通过让多个数据库键共享同一个对象来节约内存。
最后, Redis的对象带有访问时间记录信息, 该信息可以用于计算数据库键的空转时长, 在服务器启用了maxmemory功能的情况下, 空转时长较大的那些键可能会优先被服务器删除。
 
对象类型和编码
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

 
Redis使用对象来表示数据库中的键和值, 每次在Redis的数据库中新创建一个键值对时, 至少会创建两个对象, 一个对象用作键值对的键(键对象), 另一个对象用作键值对的值(值对象)。
例如, SET命令在数据库中创建了一个新的键值对, 其中键值对的键是一个包含了字符串值 "msg" 的对象, 而键值对的值则是一个包含了字符串值"hello world"的对象:
Redis中的每个对象都由一个redisObject结构表示, 该结构中和保存数据有关的三个属性分别是type属性、encoding属性和ptr属性:
 

类型

Redis 字符串对象
type
status
date
slug
summary
tags
category
icon
password
Property

 

字符串对象的编码

字符串对象的编码可以是intraw或者embstr
编码
可以用 long 类型保存的整数
int
可以用 long double 类型保存的浮点数
embstr 或者 raw
字符串值, 或者因为长度太大而没办法用 long 类型表示的整数, 又或者因为长度太大而没办法用long double 类型表示的浮点数
embstr 或者 raw
Redis 列表对象
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

 
 
列表对象的编码可以是 ziplist 或者 linkedlist 。ziplist编码的列表对象使用压缩列表作为底层实现, 每个压缩列表节点保存了一个列表元素。
如果执行以下RPUSH命令, 那么服务器将创建一个列表对象作为 numbers 键的值:
如果 numbers 键的值对象使用的是 ziplist 编码, 这个这个值对象:
notion image
 
Redis 哈希对象
type
status
date
slug
summary
tags
category
icon
password
Property
目录
目录

Redis hash(哈希散列)是由字符类型的 field(字段)和 value 组成的哈希映射表结构(也称散列表),它非常类似于表格结构。在 hash 类型中,field 与 value 一一对应,且不允许重复。
Redis hash特别适合于存储对象。一个filed/value可以看做是表格中一条数据记录;而一个key可以对应多条数据。例如,使用 hash 类型存储表格中的数据,这里以user
keyid:1为字段,name:Caovalue
通过上述方法,就把表格中的数据存储在了内存中。Redis hash 的存储结构如下图所示:
notion image
一个 hash 类型的 key 最多可以存储 (约 40 亿个)字段/值。同时 Redis hash 会为这个 key 额外储存一些附加的管理信息,比如这个键的类型、最后一次访问这个键的时间等,所以 hash 键越来越多时,Redis 耗费在管理信息方面的内存就越多。当 hash 类型移除最后一个元素后,该存储结构就会被自动删除,其占用内存也会被系统回收。
 
1
...
910111213
...
78