动态字符串SDS
c语言字符串:获取长度需要计算、非二进制安全,不可修改
redis构建了一种新的字符串结构,称为简单动态字符串(simple dynamic string)
扩容时:
如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1
如果新字符串大于1M,则新空间位扩展后字符串长度+1M+1,称为内存预分配
优点:
- 获取字符串长度的时间复杂度为O(1)
- 支持动态扩容
- 减少内存分配次数
- 二进制安全
intSet
是redis中set集合的一种实现方式,基于整数数组实现,并且具备长度可变,有序等特征
为了方便查找,redis会将inset中所有整数按照升序依次保存在contents数组中
添加元素的值大于编码上限,inset自动升级编码方式
倒序一次将数组中的元素拷贝到扩容后的正确位置
将待添加的元素放入数组末尾
特点:
- redis确保inset中的元素唯一,有序
- 具备类型升级机制,可以节省内存空间
- 底层采用二分查找方式来查询
Dict
redis是一个键值型数据库,键与值的映射关系通过dict来实现的
三部分组成:哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict)
Dict的扩容
Dict中的HashTable就是数组结合单向链表实现,当集合中元素较多时,哈希冲突增多,链表过长,查询效率大大降低
Dict在每次新增键值对时都会检查负载因子,满足以下两种情况时会触发哈希表扩容:
- 哈希表的LoadFactor>=1,并且服务器没有执行BGSAVE或者BGREWRITEAOF等后台进程
- 哈希表的LoadFactor>5
Dict在每次删除元素时,也会对负载因子做检查,当LoadFactor<0.1时,会做哈希表收缩
Dict的结构:
- 类似java中的HashTable,底层是数组加链表来解决哈希冲突
- Dict包含两个哈希表,ht[0]平常用,ht[1]用来rehash
Dict的伸缩:
- 当LoadFactor大于5或者大于1且没有子进程任务时,Dict扩容
- 当LoadFactor小于0.1是,Dict收缩
- 扩容大小为第一个大于等于used+1的2的指数
- 缩容大小为第一个大于等于used的2的指数
- Dict采用渐进式rehash,每次访问Dict时执行一次rehash
- rehash时ht[0]只减不增,新增操作只会在ht[1]执行,其他操作在两个哈希表
ZipList
ZipList是一种特殊的"双端队列",由一系列特殊编码的连续内存块组成。可以在任意一端压入/弹出操作,并且该操作的时间复杂度是O(1)
整体包含:
- zlbytes:整个压缩列表占用内存字节数
- zltail:尾结点与头结点直接字节数
- zllen:包含的节点数量
- entry:单个节点
- zlend:压缩列表末端
entry的结构:
- previous_entry_length:前一个节点长度,1或5字节(与大小有关)
- encoding:编码属性,记录content的数据类型(字符串还是整数)以及长度,占用1/2/5字节
- contents:负责保存节点的数据,可以是字符串或整数
redis中采用小端字节序(从低位向高位保存)
zl中的encoding编码分为字符串和整数两种:
字符串:如果encoding是以"00"(1字节)、"01"(2字节)、"10"(5字节)开图,则证明是字符串
整数:以"11"开始,证明content是整数,固定占用一字节
以1111开头,数据直接保存在编码中,范围从0001-1101,减1后结果为实际值
0xFF (11111111):在 ZipList 中被定义为 ZIP_END。
ziplist连锁更新问题
每个entry都包含previous_entry_length来记录上个节点大小,长度是1或者5字节
每个previous_entry_length占用1字节边缘时,前面字节的增大,可能会导致连锁更新
ziplist特性:
- 压缩列表的可以看做一种连续内存空间的"双向链表"
- 列表的节点之间不是通过指针连接,而是记录上一节点和本节点长度来寻址,内存占用较低
- 如果列表数据过多,导致链表过长,可能影响查询性能
- 增或删较大数据时可能发生连续更新问题
QuickList
问题1:zl如果内存占用较多,申请内存效率很低
限制zl的长度和entry大小
问题2:存储大量数据,超出zl最佳上限
创建多个zl来分存储数据
ql:他是一个双端链表,只不过链表中的每个节点都是一个zl
为了避免ql中的每个zl中的entry过多,redis提供了一个配置项:list-max-ziplist-size来限制
如果值为正:代表zl的允许的entry个数的最大值
如果值为负,代表zl的最大内存大小
-1:每个zl内存不能超过4kb
默认值为-2(每个zl内存不能超过8kb)
除了控制zl的大小,ql还可以对节点的zl做压缩。通过配置项list-compress-depth来控制。一般首尾不压缩。这个参数控制首尾不压缩节点个数:
0:特殊值,代表不压缩
1:首尾各有1个节点不压缩
2:首尾各有2个节点不压缩
以此推类
默认值:0
ql的特点:
- 是一个节点为zl的双端链表
- 节点采用zl,解决了传统链表的内存占用问题
- 控制了zl大小,解决连续内存空间申请效率问题
- 中间节点可以压缩,进一步节省了内存
skiplist
本质是链表,但与传统链表有几点差异:
- 元素按照升序存储
- 节点可能包含多个指针,指针跨度不同
sl的特点:
- 跳表就是一个双向链表,每个节点都包含score和ele值
- 节点按照score值排序,score值一样则按照ele字典排序
- 每个节点都可以包含多层指针,层数是1到32之间的随机数
- 不同层指针到下一个节点的跨度不同,层级越高,跨度越大
- 增删改查效率与红黑树基本一致,实现却更简单
redisObject
redis中任意数据类型的键和值都会被封装为一个redisobject,也叫做redis对象
包含:
- type:5种数据类型
- encoding:编码
- lru:lru算法标记
- refcount:引用计数器
- ptr:指向存放实际数据的空间
每一个对象都需要redisobject头,尽量少用string类型
encoding中会根据存储的数据类型不用,选择不同的编码方式,共包含11中不同类型