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

相关推荐
独断万古他化36 分钟前
【抽奖系统开发实战】Spring Boot 抽奖模块全解析:MQ 异步处理、缓存信息、状态扭转与异常回滚
java·spring boot·redis·后端·缓存·rabbitmq·mvc
七夜zippoe37 分钟前
MongoDB聚合框架与性能优化实战指南
数据库·python·mongodb·性能优化·聚合框架
聆风吟º2 小时前
金仓数据库 SQL 防火墙:内核级防护,筑牢 SQL 注入安全防线
数据库·sql·安全·金仓·kingbasees
码以致用2 小时前
StarRocks的向量数据库能力
数据库·ai
2501_945423547 小时前
Django全栈开发入门:构建一个博客系统
jvm·数据库·python
gameboy0318 小时前
从MySQL迁移到PostgreSQL的完整指南
数据库·mysql·postgresql
xdl25999 小时前
Spring Boot中集成MyBatis操作数据库详细教程
数据库·spring boot·mybatis
回到原点的码农9 小时前
Spring Data JDBC 详解
java·数据库·spring
zb200641209 小时前
Spring Boot 实战:轻松实现文件上传与下载功能
java·数据库·spring boot
CSharp精选营9 小时前
SQL Server安装避坑:这8个奇葩报错你遇到过几个?
数据库·sql server·安装指南·避坑