深度解析 Redis 存储结构及其高效性背后的机制

目录

      • 1. Redis 存储结构
        • 存储结构
        • 存储转换
      • 2. 字典实现
        • 数据结构
        • 冲突处理
        • 负载因子
      • 3. 扩容
        • 扩容步骤
        • 影响与优化
      • 4. 缩容
        • 缩容步骤
        • 优化策略
      • 5. 渐进式 Rehash
        • **渐进式 Rehash 的工作原理**
        • Rehash 规则
        • 优势
      • 6. SCAN 命令
        • SCAN 的实现原理
        • 遍历顺序
        • 避免重复和遗漏
        • 使用场景
      • 7. 过期(Expire)机制
        • 过期管理方式
        • 过期键的存储
        • 过期策略优化
      • 8. 大 Key 处理
        • 内存分配问题
        • 性能问题
        • 性能优化策略
      • 9. 跳表实现
        • 跳表的基本概念
        • 理想跳表 vs. Redis 跳表
        • 跳表的操作
        • 跳表的优势
      • 参考

1. Redis 存储结构

Redis 是一个高性能的内存数据库,主要通过多种数据结构来实现高效的数据存储和快速的查询操作。这部分将深入探讨 Redis 的不同存储结构及其内部的转换机制。

存储结构

Redis 以键值对(Key-Value)的形式存储数据,但不仅限于简单的字符串类型。它支持多种复杂的数据类型,每种数据类型都有其特定的应用场景和内部实现机制:
在这里插入图片描述

  • 字符串(String):最基本的数据类型,可以存储任何类型的数据,如文本、数字等。适用于缓存单一值或简单的计数器。

  • 哈希表(Hash):用于存储对象,例如用户信息。哈希表适合存储多个字段和值的集合,类似于关系数据库中的行。

  • 列表(List):一个双向链表,支持从两端高效地插入和删除元素。常用于实现消息队列、任务列表等。

  • 集合(Set):一个无序的、唯一的元素集合,适用于去重操作、标签系统等。

  • 有序集合(Sorted Set, ZSet):类似于集合,但每个元素都有一个分数(score),元素按照分数有序排列。适用于排行榜、按优先级排序的任务队列等。

  • 位图(Bitmap)HyperLogLog地理空间索引(Geo) 等其他高级数据结构,提供更多特定场景的优化支持。

存储转换

为了在不同的数据量和访问模式下保持高效,Redis 会根据具体情况动态调整内部存储结构:

  • 编码转换:Redis 会根据存储的数据量和访问频率,自动选择最合适的内部表示。例如,较小的哈希表可能使用压缩的 ziplist 编码,以减少内存占用,而当哈希表元素数量超过一定阈值时,会转换为普通的哈希表(dict)以提高访问效率。

  • 动态调整:当数据结构的大小或复杂度发生变化时,Redis 会动态调整底层数据结构,以确保性能和内存使用的最优化。例如,列表在元素较少时可能使用压缩列表(ziplist),元素增多后转换为双向链表。

2. 字典实现

Redis 的字典(hash table)是其核心数据结构之一,用于高效地存储和查找键值对。理解字典的内部实现对于优化 Redis 性能至关重要。

数据结构

在这里插入图片描述

Redis 字典主要由以下两个结构组成:

  • dictEntry:表示字典中的一个节点,包含三个部分:

    • 键(key):可以是任意支持的数据类型。
    • 值(value):与键对应的数据。
    • 下一个指针(next):用于链式解决哈希冲突,指向同一个哈希槽中的下一个 dictEntry
  • dictht:哈希表结构,包含以下字段:

    • 表大小(size):当前哈希表的大小,即槽(bucket)的数量。
    • 使用数量(used):当前哈希表中存储的键值对数量。
    • 指针数组(table):一个指向 dictEntry 链表的指针数组,每个槽对应一个链表的头节点。
冲突处理

哈希冲突在任何哈希表实现中都是不可避免的,Redis 采用链地址法(Separate Chaining)来解决冲突:

  • 链地址法:当多个键通过哈希函数映射到同一个槽时,这些键值对会被链接到一个链表中。dictEntry 中的 next 指针用于链接这些冲突的节点。

  • 优化链表:为了提高性能,Redis 在处理链表时采用了多种优化策略,如使用更高效的内存分配和缓存友好的数据结构,减少链表遍历的开销。

负载因子

负载因子 = used / size ; used 是数组存储元素的个数, size 是数组的长度;负载因子越小,冲突越小;负载因子越大,冲突越大;redis 的负载因子是 1 ;

负载因子是衡量哈希表使用效率的重要指标:

  • 高负载因子:意味着哈希表中每个槽平均存储更多的键值对,增加了冲突发生的概率,导致链表长度增加,从而降低查找效率。

  • 负载因子调整:为了维持哈希表的高效性,Redis 会根据负载因子动态调整哈希表的大小。当负载因子超过设定阈值时,进行扩容;当负载因子过低时,进行缩容。

3. 扩容

当字典的负载因子超过阈值(通常是 1)时,Redis 会自动进行扩容操作,以维持哈希表的高效性。
在这里插入图片描述

扩容步骤
  1. 确定新大小:通常将哈希表的大小翻倍,以减少扩容频率并保持低负载因子。

  2. 内存分配:为新的哈希表分配更大的内存空间,确保有足够的槽来存储增加的键值对。

  3. 数据迁移:将现有的键值对重新哈希并迁移到新的哈希表中。这个过程涉及将每个键重新计算哈希值,并根据新哈希表的大小将其分配到相应的槽中。

  4. 替换旧表:完成数据迁移后,新的哈希表替代旧的哈希表,释放旧表的内存空间。

影响与优化
  • 性能影响:扩容是一个耗时的操作,尤其在字典规模较大时,可能导致短暂的性能下降或阻塞。

  • 渐进式扩容:为了减少扩容对性能的影响,Redis 使用渐进式 rehash(详见第 5 节),将扩容过程分摊到多个操作中,避免一次性完成所有数据迁移。

4. 缩容

当字典的负载因子低于阈值(通常是 0.1)时,Redis 会进行缩容操作,以释放不必要的内存资源。
在这里插入图片描述

缩容步骤
  1. 确定新大小:根据当前键值对的数量,计算出一个最小且适合的哈希表大小,避免过度缩小导致频繁的扩缩容操作。

  2. 内存释放:将哈希表大小调整为新的尺寸,释放多余的内存空间。

  3. 数据迁移:与扩容类似,将现有的键值对重新哈希并迁移到新的哈希表中,以适应缩小后的槽数量。

  4. 替换旧表:新的哈希表替代旧的哈希表,释放旧表的内存。

优化策略
  • 避免频繁缩容:为防止由于负载因子频繁波动导致的频繁扩缩容,Redis 采用了渐进式调整和阈值保护机制。

  • 最小缩容步长:设置最小缩容步长,确保每次缩容都能显著减少内存占用,避免频繁的微调。

5. 渐进式 Rehash

为了避免一次性 rehash 导致的性能问题,Redis 采用了渐进式 rehash 技术,将 rehash 过程分散到多个客户端操作中进行。

渐进式 Rehash 的工作原理
  1. 双哈希表:在进行 rehash 时,Redis 同时维护两个哈希表:旧表和新表。新表的大小通常是旧表的两倍或一半,取决于是扩容还是缩容。

  2. 分步迁移:每次客户端执行字典操作(如插入、删除、查找)时,Redis 会迁移部分数据从旧表移动到新表。这些迁移步骤是渐进的,逐步完成整个 rehash 过程。

  3. 操作兼容:在 rehash 过程中,新的插入操作会直接插入到新表,而查找操作则会同时查询旧表和新表,确保数据的一致性。

  4. 完成迁移:当所有数据迁移完成后,旧表被释放,新的哈希表完全接管数据存储。

Rehash 规则
  • 元素重新映射:每个键通过哈希函数重新计算其在新表中的位置,根据新表的大小将其分配到相应的槽中。

  • 渐进迁移:每次操作迁移一定数量的键值对,确保不会一次性占用过多的 CPU 资源,避免阻塞其他客户端请求。

优势
  • 避免阻塞:渐进式 rehash 将 rehash 过程分散到多个小步骤中,避免了长时间的阻塞,提高了 Redis 的响应性。

  • 平滑过渡:在 rehash 过程中,Redis 依然可以正常处理客户端请求,确保服务的连续性和稳定性。

6. SCAN 命令

SCAN 命令是 Redis 提供的一种用于遍历数据的迭代器,旨在替代 KEYS 命令,以实现更高效和安全的键遍历操作。
在这里插入图片描述

SCAN 的实现原理
  • 游标(Cursor)机制SCAN 使用一个游标来记录遍历的进度。客户端每次调用 SCAN 时,提供上一次返回的游标,服务器根据游标继续遍历剩余的键。

  • 非阻塞遍历:与 KEYS 一次性返回所有键不同,SCAN 分批次返回部分键,避免了阻塞服务器和客户端的情况。

遍历顺序
  • 伪随机顺序SCAN 并不保证键的遍历顺序,而是以伪随机的方式返回键。这是因为 Redis 使用哈希槽和渐进式 rehash,遍历顺序可能会因哈希表的动态变化而变化。

  • 高位进位加法:为了在不同调用之间有效地管理游标,SCAN 使用一种高位进位加法的方式来确保遍历的连续性和完整性。

避免重复和遗漏
  • 一致性保证:Redis 通过特定的算法确保在遍历过程中尽量避免重复和遗漏键,即使在遍历过程中发生了哈希表的变化。

  • 极端情况:在某些极端情况下,如 SCAN 过程中发生多次缩容或扩容,可能会导致部分键重复返回或遗漏。这种情况虽然罕见,但需要在应用层进行适当的处理。

使用场景
  • 大规模数据遍历:适用于需要遍历大量键但不希望影响服务器性能的场景,如数据导出、统计分析等。

  • 实时应用:由于 SCAN 是非阻塞的,适合在实时应用中使用,避免长时间的阻塞操作影响用户体验。

7. 过期(Expire)机制

Redis 提供了键的过期机制,允许在指定时间后自动删除键,以实现数据的自动过期和内存的自动回收。

过期管理方式

Redis 通过两种主要方式来管理过期键:

  • 惰性删除(Lazy Deletion)

    • 工作原理:当客户端访问一个键时,Redis 会检查该键是否设置了过期时间。如果过期时间已到,Redis 会立即删除该键,并返回不存在的结果。
    • 优点:节省资源,只在需要时进行检查和删除,适用于不经常访问的键。
    • 缺点:如果过期键长时间不被访问,可能会占用内存,导致内存泄漏。
  • 定时删除(Active Deletion)

    • 工作原理:Redis 定期随机扫描一部分设置了过期时间的键,并删除过期的键。这种扫描过程由 Redis 的后台线程自动执行,不依赖于客户端的访问操作。
    • 优点:能够及时回收过期键,避免内存被无效数据占用。
    • 缺点:可能会带来一定的 CPU 开销,特别是在大量键设置了过期时间时。
过期键的存储
  • 单独数据结构:Redis 使用专门的数据结构(如一个有序集合)来存储所有设置了过期时间的键及其对应的过期时间。

  • 高效查找:通过有序集合的特性,Redis 能够高效地查找和删除过期键,确保过期管理的性能。

过期策略优化
  • 最少过期时间的键优先删除:在进行定时删除时,Redis 会优先删除最早过期的键,以最大化内存回收的效率。

  • 批量删除:为了减少每次删除操作的开销,Redis 通常会批量删除多个过期键,而不是一次删除一个。

8. 大 Key 处理

在 Redis 中,“大 Key”指的是包含大量数据的键,如包含大量元素的哈希表、有序集合或列表。这些大 Key 在操作时可能会带来显著的性能问题,需要特别注意和优化。

内存分配问题
  • 扩容时的内存分配:大 Key 在进行扩容(如哈希表扩容或跳表扩容)时,需要一次性分配大量内存。这可能导致 Redis 出现短暂的卡顿或内存分配失败。

  • 删除时的内存回收:一次性删除大 Key 需要释放大量内存,可能会引发内存碎片或 GC(如果使用了内存管理机制)的额外开销。

性能问题
  • 高延迟操作:对大 Key 的操作,如大批量插入、删除或查询,可能会导致高延迟,影响 Redis 的整体性能和响应时间。

  • 阻塞问题:某些操作如重写 AOF(Append-Only File)或 RDB(Redis Database)快照时,如果包含大 Key,可能会导致 Redis 阻塞时间增加。

性能优化策略
  • 分片存储:将大 Key 分片存储为多个较小的 Key,分散存储负载,减少单个 Key 的大小。例如,将一个大型列表分割为多个小列表。

  • 批量操作优化:使用流水线(Pipeline)或事务(Transaction)等机制,批量处理大 Key 的操作,减少网络往返和操作延迟。

  • 异步处理:对于耗时的操作,可以考虑使用异步处理或后台任务,避免阻塞主线程。

  • 内存优化:选择合适的数据结构和编码方式,尽量减少大 Key 的内存占用。例如,使用压缩列表(ziplist)来存储小规模的哈希表或列表。

9. 跳表实现

Redis 使用跳表(Skip List)作为有序集合(Sorted Set, ZSet)的底层实现,以提供高效的有序数据存储和快速的增删查改操作。
在这里插入图片描述

跳表的基本概念

跳表是一种基于多层链表的数据结构,通过在多个层级上建立跳跃指针,实现快速的查找和遍历操作。其主要特点包括:

  • 多层结构:每个元素在不同层级上都有可能存在跳跃指针,层级越高,跳跃跨度越大。

  • 随机化:元素在跳表中的层级通常是随机决定的,这种随机化策略保证了跳表在平均情况下具有良好的性能。

  • 时间复杂度:跳表的查找、插入和删除操作的平均时间复杂度为 (O(\log N)),与平衡树相当,但实现更为简单。

理想跳表 vs. Redis 跳表
  • 理想跳表

    • 结构:每隔一个节点增加一个层级,形成类似二叉树的结构。
    • 性能:在理想情况下,跳表能够高效地保持有序性和快速的操作性能。
  • Redis 跳表

    • 扁平化设计:为了节省内存,Redis 的跳表采用了更扁平化的结构,限制了跳表的最大层级为 32 层。这与理想跳表的多层级结构有所不同,但在实际应用中仍能提供高效的操作性能。

    • 层级生成:Redis 使用概率机制(通常为 1/4 的概率提升到更高层级)生成跳表的层级,确保跳表在大多数情况下具有良好的平衡性和性能。

    • 节点结构:每个跳表节点包含指向下一个节点的指针,以及用于排序的分数(score)和对应的成员(member)。

跳表的操作
  • 查找(Search):从最高层级开始,依次向右移动,直到找到第一个大于或等于目标分数的节点,逐层向下直到最低层级。

  • 插入(Insert):首先确定新节点在每个层级上的位置,然后插入新的跳跃指针,维护跳表的有序性。

  • 删除(Delete):查找到要删除的节点,在所有层级上移除其跳跃指针,维护跳表的结构完整性。

跳表的优势
  • 内存效率高:相比于平衡树,跳表的实现更为简单,且内存占用更少。

  • 实现简便:跳表的代码实现相对简单,易于维护和优化。

  • 性能稳定:在大多数情况下,跳表能够提供与平衡树相当的性能,且由于其随机化的结构,避免了最坏情况的性能下降。

参考

0voice · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/448619.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电子商务网站维护技巧:保持WordPress、主题和插件的更新

在这个快节奏的数字时代,维护一个电子商务网站的首要任务之一是保持WordPress、主题和插件的最新状态。过时的软件不仅可能导致功能故障,还可能带来安全风险。本文将深入探讨如何有效地更新和维护您的WordPress网站,以确保其安全性和性能。 …

工业物联网关-ModbusTCP

Modbus-TCP模式把网关视作Modbus从端设备,主端设备可以通过Modbus-TCP协议访问网关上所有终端设备。用户可以自定义多条通道,每条通道可以配置为TCP Server或者TCP Slave。注意,该模式需要指定采集通道,采集通道可以是串口和网口通…

简述微服务高可用之Sentinel、Seate

简述微服务高可用之Sentinel、Seate使用 下文主要讲述使用sentinel,如何降级限流熔断及如何使用seata管理分布式事务 sentinel服务端安装与使用 1、下载 进入https://github.com/alibaba/Sentinel/releases 根据你的需求进行下载对应版本 我这里是JDK17 下载的1.8.8版本&am…

【数据结构与算法】链表(上)

记录自己所学&#xff0c;无详细讲解 无头单链表实现 1.项目目录文件 2.头文件 Slist.h #include <stdio.h> #include <assert.h> #include <stdlib.h> struct Slist {int data;struct Slist* next; }; typedef struct Slist Slist; //初始化 void SlistI…

【C#】WPF MVVM 简单示例代码

1. 目录结构 2. 代码 2.1 DelegateCommand.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input;namespace MVVSSample.Commands {class DelegateCommand : ICommand{publ…

信息安全工程师(52)网络安全审计系统组成与类型

前言 网络安全审计系统是一种用于监控、分析和报告网络环境中安全事件的系统。其组成与类型均体现了对网络安全性的全面考虑和细致划分。 一、网络安全审计系统的组成 网络安全审计系统一般由以下几个关键部分组成&#xff1a; 审计数据采集系统&#xff1a;负责采集被审计系统…

shell案例之一键部署kafka

Shell案例之一键部署kafka 一、案例问题 &#xff08;1&#xff09;Kafka是用Java编写的&#xff0c;需要配置jdk环境变量 &#xff08;2&#xff09;Kafka配置文件数目多 &#xff08;3&#xff09;命令安装繁琐 二、案例分析&#xff1a; &#xff08;1&#xff09;检查…

elementUI,设置日期,只能选择过去的和今天的日期

在 el-date-picker 组件中加&#xff1a;:picker-options"pickerOptions" <el-form-item label"票据生成日期&#xff1a;"> <el-date-picker v-model"date1" type"daterange" range-separator"至" value-format&…

chatgpt搭建大模型技术知识解读与总结

搭建大型语言模型&#xff08;如ChatGPT&#xff09;的技术知识涉及多个领域&#xff0c;包括机器学习、自然语言处理&#xff08;NLP&#xff09;、深度学习、数据处理等。下面是一些关键概念和步骤的总结&#xff1a; ### 1. **基础知识** #### a. **自然语言处理 (NLP)** …

基于Qt/QChart实现折线图和散点图的绘制示例程序解析

1. 项目简介 本文讲解的是一个基于Qt框架的QChart模块实现的折线图与散点图结合的绘制程序。程序通过自定义类LineChartWithGradient实现折线图、散点图以及带有渐变填充的区域图&#xff0c;最终形成一个美观的数据可视化效果。 2. 类构造函数 LineChartWithGradient::LineC…

天锐绿盾VS Ping32数据安全新选择,用户体验分享

随着网络威胁日益严重&#xff0c;如何保护个人和企业的网络安全成为了一个迫在眉睫的问题。天锐绿盾和Ping32作为市场上两款备受欢迎的网络安全软件&#xff0c;各自拥有独特的特点和功能。本文将对这两款软件进行深入的使用体验分享&#xff0c;帮助用户做出最佳选择。 防护性…

Docker 拉取镜像时配置可用镜像源(包含国内可用镜像源)

文章目录 写在前面一、Docker 官方源二、更换Docker 国内可用镜像源 &#xff08;推荐使用&#xff09;参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04&#xff0c;docker-27.3.1 一、Docker 官方源 打开 /etc/docker/daemon.json文件&#xff1a; sudo gedit …

3.Three.js程序基本框架结构和API说明

Three.js程序基本框架结构和API说明 1.基本框架结构代码 一个基本的Three.js程序&#xff0c;基本都需要设置场景、渲染器、相机、灯光等等通用操作&#xff0c;因而我们可以把Three.js基本程序框架进行整理&#xff0c;如下。其中&#xff0c;我们可以用Three.js提供的Orbit…

JAVA 中的克隆对象

克隆对象就是复制一个一模一样的对象&#xff0c;但是复制出来的对象和原对象不是同一个对象&#xff0c;是两个对象&#xff0c;只不过复制过来的对象和原对象除了内存地址之外&#xff0c;其它的属性一模一样。 在超类 Object 中有一个 clone() 方法&#xff1a; protected…

NC 单据模板自定义项 设置参照(自定义参照)

NC 单据模板自定义项 设置参照&#xff08;自定义参照&#xff09; 如图下图&#xff0c;NC 单据模板自定义项 设置参照&#xff1a; 1、选择需要设置参照的自定义字段&#xff0c;选择高级属性页签&#xff0c;在类型设置中&#xff0c;数据类型选择参照信息&#xff0c;即bd…

Ubuntu-Ubuntu22.04下Anacodna3的qmake和Qt的qmake冲突问题

Ubuntu22.04下Anacodna3的qmake和Qt的qmake冲突问题 一、问题描述二、原因分析三、解决办法 一、问题描述 Ubuntu22.04下Anacodna3的qmake和Qt的qmake冲突问题 zhyzhy-HP:~/Sources/mpv-examples/libmpv/qt$ make g -c -pipe -g -Wall -Wextra -D_REENTRANT -fPIC -DQT_WIDGET…

python 基础笔记(其实有点内容的)

print(math.gamma(n)) # 求 (n-1) 的阶乘 数值, 数值计算 format(50, “b”) bin(50)[2:]&#xff0c; 这个“b” 就代表的是 binary format(14, ‘b’) ------> ‘1110’ 去除 0b 去掉前导零 str(000001) # 只适合python2.x ‘1’ “00000001”.lstrip(“0”) # python3…

图论day62|拓扑排序理论基础、117.软件构建(卡码网)、最短路径之dijkstra理论基、47.参加科学大会(卡码网 第六期模拟笔试)

图论day62|拓扑排序理论基础、117.软件构建&#xff08;卡码网&#xff09;、最短路径之dijkstra理论基、47.参加科学大会&#xff08;卡码网 第六期模拟笔试&#xff09; 拓扑排序理论基础117.软件构建&#xff08;卡码网&#xff09;最短路径之dijkstra理论基础47.参加科学大…

AI控制工业机器人入门教程

简介 AI控制的工业机器人正在改变现代制造业的面貌。与传统的编程控制不同&#xff0c;AI使机器人能够通过感知环境、自主决策和学习不断优化自身的操作。这篇教程将介绍实现AI控制工业机器人的必要知识和技能&#xff0c;帮助读者从基础开始构建起AI控制机器人的理解和能力。…

OceanBase + DolphinScheduler,搭建分布式大数据调度平台的实践

本文整理自白鲸开源联合创始人&#xff0c;Apache DolphinScheduler PMC Chair&#xff0c;Apache Foundation Member 代立冬的演讲。主要介绍了DolphinScheduler及其架构、DolphinScheduler与OceanBase 的联合大数据方案。 DolphinScheduler是什么&#xff1f; Apache Dolphi…