redis原理篇(基本数据结构)

动态字符串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包含两个哈希表,ht0平常用,ht1用来rehash

Dict的伸缩:

  • 当LoadFactor大于5或者大于1且没有子进程任务时,Dict扩容
  • 当LoadFactor小于0.1是,Dict收缩
  • 扩容大小为第一个大于等于used+1的2的指数
  • 缩容大小为第一个大于等于used的2的指数
  • Dict采用渐进式rehash,每次访问Dict时执行一次rehash
  • rehash时ht0只减不增,新增操作只会在ht1执行,其他操作在两个哈希表

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中不同类型

相关推荐
Databend4 小时前
Agent 轨迹分析与归因的数据工程实践
大数据·数据库·agent
这个DBA有点耶4 小时前
SQL改写进阶:标量子查询的“隐形代价”与消除实战
数据库·mysql·架构
smallyoung6 小时前
数据库乐观锁深度解析:MySQL、PostgreSQL 实战 + Spring Boot 集成指南
数据库·mysql·postgresql
parade岁月6 小时前
MySQL JOIN解析:朴实无华但食之有味
数据库·后端
用户3169353811836 小时前
MySQL服务无法启动问题解决全记录
数据库
vivo互联网技术10 小时前
从 10 分钟到 1 秒:ES 深度分页任意跳页的三轮优化实战
服务器·数据库·redis·elasticsearch·深度分页
倔强的石头_1 天前
《Kingbase护城河》——猎捕慢查询:执行计划的微观解析与索引调优实战
数据库
SelectDB1 天前
Apache Doris Python UDF:让 SQL 直接调用 Python 生态,支撑 Agent 时代复杂业务逻辑
大数据·数据库·python
jiayou642 天前
KingbaseES 表级与列级加密完全指南
数据库·后端