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包含两个哈希表,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中不同类型

相关推荐
没有bug.的程序员1 小时前
电商秒杀系统深度进阶:高并发流量建模、库存零超卖内核与 Redis+MQ 闭环
数据库·redis·缓存·高并发·电商秒杀·流量建模·库存零超卖
先做个垃圾出来………2 小时前
Python常见文件操作
linux·数据库·python
奶茶树2 小时前
【数据结构】红黑树
数据结构·c++·算法
轩情吖2 小时前
MySQL库的操作
android·数据库·mysql·oracle·字符集·数据库操作·编码集
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-02-25
数据库·人工智能·经验分享·神经网络·chatgpt
Flobby5292 小时前
深入理解 MySQL 锁:从全局锁到死锁检测
数据库·后端·mysql
是小崔啊2 小时前
MySQL22 - 分库分表的聚合问题
数据库
玖雨y2 小时前
【DDIA】存储和查询
数据库·后端·存储·ddia
蒸蒸yyyyzwd2 小时前
redis实战学习笔记p1-12
数据库·笔记