- 简单动态字符串(SDS)
在redis中,包含字符串值的键值对都是由SDS实现的
值得注意的是,redis中的键值对的键都是一个字符串对象
SDS定义:
SDS遵循c字符串以空字符结尾的规则,这样做的好处就是可以利用一部分C语言的库函数
SDS与C字符串的区别:
- SDS字符串以常数级别复杂度获取字符串长度(长度保存在属性len中),而C字符串获取长度需要O(n)复杂度
- SDS杜绝缓冲区溢出,C因为不记录自身长度,所以有缓冲区溢出的风险;C在连接字符串时就直接加到后边,而SDS首先会检查空间是否满足要求,若不满足会自动扩展至修改所需大小
- SDS减少了修改字符串时带来的内存重分配次数
为了避免运行速度和性能问题,SDS实现了空间预分配和惰性空间释放两种优化措施
- 二进制安全 C字符串的中间不能包含空字符,否则会被认为是字符串的结束,这就使得C字符串不能保存图片音频视频这样的二级制数据,而SDS字符串使用len属性来判断是否结束,并不是用空字符判断
- 兼容部分C字符串函数
- 字典
一个键(key)和一个值(value)关联,字典中的每个键都是独一无二的,这数据结构也是redis中应用最广泛的,除了用来表示数据库之外,还用来实现哈希键
字典的实现:
用哈希表作为底层实现,一个哈希表里有多个哈希节点,每个哈希节点就保存一个键值对
字典:
Redis使用MurmurHash2算法作为计算哈希键的算法
Redis使用链地址法解决哈希冲突问题
哈希表的扩展与收缩:
当哈希表的负载因子小于0.1时,自动开始收缩
渐进式rehash:
Rehash需要数组迁移,当数据量小的时候还好说,但数据量大的时候,明显达不到性能要求,所以服务器会分多次,渐渐的将数组的迁移工作完成。
渐进式rehash期间的哈希表操作会同时使用两个哈希表ht【0】和ht【1】,找键时会在两个里边都找,而添加操作只针对ht【1】,这一操作保证ht【0】里的键会越来越少
- 跳跃表
跳跃表的实现:
跳跃表节点:
- 层 level数组包含多个元素,每个元素都有一个指向其他节点的指针
- 前进指针 每一层都有一个指向表尾方向的指针(level【i】.forward),用于从表头向表尾方向访问节点
- 跨度 层的跨度(level【i】.span属性)用于记录两个节点之间的距离
两个节点之间的跨度越大,他们离得越远
指向Null的所有前进指针跨度都为0,不连接任何节点
- 后退指针 用于从表尾指向表头,但不同于前进指针的是,一次只能后退至前一个节点
- 分值和成员 分值(score)是一个double类型的浮点数,跳表中的节点都按分值从小到大来排序 成员对象(obj)是一个指针,指向一个字符串对象(保存着SDS值)
跳跃表:
通过一个zskiplist结构来持有跳跃表节点,方便处理
- 整数集合
整数集合的实现:
Contents是底层实现,每个元素都是contents数组的一个项,且按从小到大有序排列,无重复。
Length记录整数集合包含的元素数量(长度)
Encoding决定了数组存放的是哪个类型的整数(8 16 32)
升级操作:
升级的几个好处:
提升灵活性:可以随意添加数据,不必担心出现类型错误
节约内存:可以确保集合能同时保存三种不同类型,且升级操作只在必要时出现
整数集合不支持降级操作
- 压缩列表
压缩列表的构成
压缩列表节点的构成:
Previous-entry-length
以字节为单位,记录压缩列表前一个节点的长度
Encoding
记录了节点的content所保存的数据类型以及长度:一字节两字节或五字节长,值的最高位为00、01、10的是字节数组编码;一字节长,值的最高位以11开头的是整数编码
Content
保存节点的值,节点值可以是一个字节数组或者整数
连锁更新:
描述的是多个节点长度都在254附近,此时一个大于254的节点加入进来,那么前面所说的Previous-entry-length就会从一字节变为五字节,跟多米诺骨牌一样导致全体节点都需要扩容升级,删除节点同样也会导致此操作。
但这种场景出现的几率不高
- 对象
Redis主要根据前面的几种数据结构构成一个对象系统,包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象五种。
对象的类型与编码:
当我们在数据库中创建对象时,至少会创建两个对象,一个是键一个是值
Type类型记录了对象的类型(那五个之一),对redis来说,键总是一个字符串对象。
用type命令时,返回的是值对象的类型,不是键对象的
对象的ptr指针指向底层实现的数据结构,这些数据结构由对象的encoding属性决定:
每种类型的对象都使用了至少两种不同的编码,这一举措提高了灵活性:
字符串对象:
编码的转换:
Int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下会被转换成raw编码的字符串对象。
对于int字符串来说,比如我们执行一个append操作,由于这个操作只能对字符串执行,此时就会将保存的整数转换为字符串
Embstr是只读的,也就是说我们对他操作一定会变成raw的
列表对象:
列表对象的编码可以是ziplist或linkedlist
Ziplist编码的列表对象使用的是压缩列表