《Redis设计与实现》笔记之数据结构与对象
数据结构与对象
SDS
SDS(simple dynamic string): 简单动态字符串
SDS定义:
SDS与C字符串区别:
- C字符串并不记录自身的长度信息,SDS有len属性。
- C字符串容易缓冲区溢出,SDS不会,SDS会先检查空间是否满足需要,不够扩展空间
- SDS字符串可以减少修改字符串时带来的内存重分配次数。
- C字符串如增长字符串忘记内存重分配来扩展底层数组空间大小,会产生缓冲区溢出;如是缩短字符串操作,忘记内存重分配释放不需要的空间,会产生内存泄漏。而频繁内存重分配,影响性能。
- SDS实现了空间预分配和惰性空间释放两种优化策略
- 空间预分配:1)修改后,len小于1MB,那么程序将分配和len属性同样大小的未使用空间。2)修改后,len大于1MB,那么程序会分配1MB未使用空间
- 惰性空间释放:程序不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
- C字符串里不能包含空字符串,所以只能保存文本,而不能保存图片、音频、视频、压缩文件等二进制文件。SDS字符串可以,因为使用len属性来判断字符串是否结束,而不是空字符串('\0')
链表
redis的list底层数据结构就是使用双向链表(双端无环)
字典
SET、HASH底层
哈希表节点
字典
redis的哈希表使用拉链法来解决hash冲突,程序总是将新节点添加到链表的表头位置,提升速度
渐进式rehash: 好处在采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加删除查找和更新操作上
跳表
redis使用跳表有两个地方,一是ZSET有序集合底层结构,另一个则是集群节点中用作内部数据结构
在大部分情况下,跳表效率可以和平衡树相媲美,并且实现更简单。
整数集合(intset)
整数集合是集合键底层实现之一
升级:每当我们要将新元素添加到整数集合中,并且新元素的类型比整数集合现有所有元素类型都要长,整数集合需先进行升级,然后才能将新元素添加到整数集合中去。
升级过程:
- 根据新元素的类型,扩展集合底层数组的空间大小,并为新元素分配空间
- 将底层数组现有的所有元素都转换成与新元素相同的类型,将类型转换后的元素放置到正确的位置,在放置元素的过程中,需要维持底层数组的有序性质不变。
- 将新元素添加到底层数组中
升级好处:
- 提升整数集合灵活性,可以随意将int16_t、int32_t等整数添加到集合,而不用担心出现类型错误
- 尽可能节约内存
注意:整数集合不支持降级操作,一旦对数组进行升级操作,编码就会一直保持升级后的状态。
压缩列表
压缩列表(ziplist)是列表键和哈希键的底层实现之一。
压缩列表节点
encoding和content
连锁更新
对象
Redis并没有直接使用上面数据结构来实现键值对数据库,而是基于数据结构创建了一个对象系统,包含字符串对象、列表对象、哈希对象、集合对象、和有序集合对象五种。
Redis对象系统还实现了基于引用计数技术的内存回收机制;另Redis还通过引用计数技术实现对象共享机制,这一机制可以在适当条件下,通过让多个数据库键共享同一个对象来节约内存。
在列表对象包含的元素比较少时,redis使用压缩列表作为列表对象的底层:
- 压缩列表比双端链表更节约内存,且在元素比较少时,在内存中以连续块方式保存的压缩列表比双端链表更快被载入到缓存中
- 随着列表元素增多,使用压缩列表优势消失,底层实现转向功能更强也更适合保存大量元素的双端链表
字符串
可以用long、double类型表示的浮点数在redis也是作为字符串值来保存的
embstr专门用于保存短字符串的一种优化编码方式;
如果字符串对象保存的是一个字符串值,且字符串长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个值,编码设置为raw
列表对象
当列表对象同时满足以下两个条件,列表对象使用ziplist编码;不能满足这两个条件的使用linkedlist编码:
- 列表对象保存的所有字符串元素长度都小于64字节
- 列表对象保存的元素数量小于512个
哈希对象
当哈希对象同时满足以下两个条件,哈希对象使用ziplist编码,否则使用hashtable编码
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
- 哈希对象保存的键值对数量小于512个
- 使用压缩列表实现
- 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后
- 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,依次往尾部添加
- 使用字典键值实现
集合对象
当集合对象同时满足以下两个条件时,使用intset编码,否则使用hashtable:
- 集合对象保存的所有元素都是整数值
- 集合对象保存的元素数量不超过512个
有序集合对象
当有序集合对象同时满足以下两个条件时,使用ziplist编码,否则使用skiplist:
- 集合对象保存的所有元素成员的长度都小于64字节
- 集合对象保存的元素数量小于128个
对象共享
在redis中让多个键共享同一个值对象需要执行以下两步:
- 将数据库键的值指针指向一个现有值对象
- 将被共享的值对象的引用计数加一
对象的空转时长
redisObject包含一个lru属性,该属性记录了对象最后一次被命令程序访问的时间;
另一项作用是,服务器如果打开了maxmemory选项,并且服务器用于回收内存算法为volatile-lru或者allkeys-lru,那么当服务器占有内存超过maxmemory,空转时长高的那部分键将被服务器优先释放,从而回收内存。