news 2026/4/18 8:02:29

Redis数据类型的底层实现和数据持久化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis数据类型的底层实现和数据持久化

一、五大基础数据类型的底层实现原理

Redis 之所以快,除了它是基于内存的操作外,更重要的是它为每种数据类型都精心设计了底层的编码。Redis 不是简单的 "Key-Value" 存储,它会根据数据量的多少和类型的不同,动态选择最节省内存或最高效的数据结构。

本文将带你通过源码视角,通过redisObject对象出发,解析 String、List、Hash、Set、ZSet 的底层实现。

核心对象:redisObject

在 Redis 中,我们存储的每一个键值对,其 Value 并不是直接存储为原始数据,而是被封装在一个redisObject结构体中。

typedef struct redisObject { unsigned type:4; // 类型:String, List, Hash... unsigned encoding:4; // 编码:int, raw, embstr, ziplist... unsigned lru:24; // LRU 时间戳(用于淘汰策略) int refcount; // 引用计数 void *ptr; // 指向底层数据结构的指针 } robj;

关键点:type决定了对外的数据类型,而encoding决定了内部真正的底层实现。

1. String (字符串)

String 是 Redis 最基本的数据类型。

底层实现

  • int: 如果字符串本身是整数且在 Long 范围内,Redis 会直接存储为整数,避免分配额外的内存。

  • SDS (Simple Dynamic String): 如果存储的是文本字符串,Redis 使用自定义的 SDS 结构,而不是 C 语言的字符串。

SDS 的优势

  1. O(1) 获取长度: SDS 头部维护了len属性。

  2. 二进制安全: 可以存储图片、视频流数据(不以\0为结束符)。

  3. 杜绝缓冲区溢出: 修改字符串时会自动检测空间。

编码格式 (Encoding)

  • int: 存储整数。

  • embstr: 存储短字符串(<= 44 字节)。它将redisObjectSDS连续分配在同一块内存中,减少内存碎片和分配次数。

  • raw: 存储长字符串(> 44 字节)。redisObjectSDS分开分配。

2. List (列表)

List 用于存储有序的字符串列表。

底层实现演进

  1. 早期版本: 数据少时用Ziplist (压缩列表),数据多时用Linkedlist (双向链表)

  2. 现代版本 (3.2+): 统一使用Quicklist

核心结构解析

  • Ziplist: 一块连续的内存空间,没有指针的开销,极其节省内存,但插入删除需要内存重排,不适合大量数据。

  • Quicklist: 是 "Linkedlist + Ziplist" 的结合体。它是一个双向链表,但链表中的每个节点不再是一个单独的元素,而是一个Ziplist

    • 优势: 既保留了链表的插入效率,又利用 Ziplist 减少了内存碎片。

: 在 Redis 7.0 之后,listpack逐渐取代了ziplist,但在逻辑上依然是 "链表 + 紧凑列表" 的组合。

3. Hash (哈希)

Hash 用于存储对象(键值对集合)。

底层实现

  • Ziplist / Listpack (压缩列表):

    • 触发条件: 元素数量少(默认 < 512)且所有元素的值都较短(默认 < 64 字节)。

    • 原理: Key 和 Value 作为两个节点紧挨着存入压缩列表。查找时需要遍历,时间复杂度 $O(N)$,但因为数据量小,CPU 消耗可忽略,换取的是极高的内存利用率。

  • Hashtable (字典/哈希表):

    • 触发条件: 数据量大或元素值过长。

    • 原理: 使用 MurmurHash 算法计算哈希值,通过链地址法解决冲突。

    • 扩容机制: 采用渐进式 Rehash,避免一次性拷贝大量数据导致 Redis 阻塞。

4. Set (集合)

Set 用于存储不重复的元素。

底层实现

  • Intset (整数集合):

    • 触发条件: 集合中所有元素都是整数,且元素数量较少(默认 < 512)。

    • 原理: 底层是一个有序的数组。查找时使用二分查找,时间复杂度 $O(\log N)$。

  • Hashtable (字典):

    • 触发条件: 元素包含非整数,或数量过多。

    • 原理: 利用字典的 Key 存储元素,Value 统一指向 NULL。

5. ZSet (有序集合)

ZSet 是 Redis 最具特色的数据结构,它维护了元素的 Score 从而实现排序。

底层实现

  • Ziplist / Listpack:

    • 触发条件: 元素少且短。

    • 原理: 元素和 Score 紧挨着存储,按 Score 从小到大排序。

  • Skiplist + Hashtable (跳表 + 字典):

    • 触发条件: 元素多或长。

    • 这是 ZSet 性能强大的核心。

为什么是 Skiplist + Hashtable?

Redis 在 ZSet 中同时使用了这两种结构(封装在zset结构体中):

  1. Hashtable: 存储Member -> Score的映射。保证了ZSCORE命令原本 $O(1)$ 的时间复杂度。

  2. Skiplist: 存储有序的节点。跳表是一种典型的 "空间换时间" 结构,通过维护多层索引,实现 $O(\log N)$ 的范围查询(如ZRANGE)。

    • 为什么不用红黑树?跳表实现更简单,且在并发环境下(虽然 Redis 是单线程,但设计思路上)区间查找效率通常优于平衡树。

底层映射

数据类型数据量少/元素小 (Encoding)数据量大/元素大 (Encoding)
Stringint/embstrraw
Listziplist(旧) /quicklist(新)quicklist
Hashziplist/listpackhashtable
Setintsethashtable
ZSetziplist/listpackskiplist+hashtable

二、Redis数据持久化

Redis 是内存数据库,一旦断电或进程重启,内存中的数据就会丢失。为了解决这个问题,Redis 提供了持久化机制,将内存数据写入磁盘。

1. RDB - 内存快照

RDB 是 Redis 默认的持久化方案。它就像给数据库拍了一张“照片”。

核心原理

在指定的时间间隔内(例如“60秒内有1000个改动”),Redis 会执行bgsave命令。

  • Fork 子进程:Redis 父进程 fork 出一个子进程。

  • Copy-on-Write (写时复制):这是 RDB 不阻塞主线程的关键。子进程共享父进程的内存数据,只有当父进程修改某块数据时,OS 才会为该块数据复制一份副本给子进程。

  • 写入文件:子进程将内存数据写入临时的 RDB 文件,完成后替换旧文件。

优缺点分析

  • 优点

    • 恢复速度极快:RDB 是紧凑的二进制文件,Redis 加载它比加载文本格式的 AOF 快得多。

    • 适合冷备:文件小,易于传输,适合用于灾难恢复(Disaster Recovery)。

  • 缺点

    • 数据丢失风险:因为是定时快照,如果 Redis 意外宕机,你会丢失最后一次快照后的所有修改(可能几分钟的数据)。

    • 重量级操作fork操作在内存很大(如几十 GB)时会阻塞主线程毫秒甚至秒级。

2.AOF —— 操作日志

核心机制

  • 记录方式:以文本协议格式记录所有的写命令(Write/Set/Del)。

  • 刷盘策略 (appendfsync) —— 这是业务调优的重点:

    • Always:每条命令都刷盘。数据最安全,但性能极差(不推荐)。

    • Everysec:每秒刷盘一次。默认策略,兼顾性能与安全(最多丢1秒数据)。

    • No:操作系统决定何时刷盘。

AOF 重写 (Rewrite)

  • 痛点:随着时间推移,日志文件会变得无限大(比如反复INCR一亿次,日志有一亿条,但最终状态只是一个数字)。

  • 解决:Redis 后台重写 AOF 文件,只保留恢复当前状态所需的最小命令集。

业务优缺点

  • 优点(业务侧):

    • 数据安全性高:最多只丢失 1 秒数据。

  • 缺点(业务侧):

    • 文件体积大:通常比 RDB 文件大很多。

    • 恢复速度慢:重启时需要一条条重放命令,数据量大时启动非常慢。

3.业务场景中的策略选择

场景 A:纯缓存模式

  • 业务特征:数据均可从数据库(MySQL/PostgreSQL)重新加载;缓存丢失不影响业务正确性,只影响短暂的性能。

  • 策略:关闭 RDB 和 AOF。

  • 理由:追求极致性能,节省磁盘 IO。

场景 B:可以容忍少量数据丢失,追求高性能

  • 业务特征:比如论坛的点赞数、页面的访问统计、非关键的日志分析。

  • 策略:仅使用 RDB。

  • 配置建议:比如每 15 分钟存一次。

  • 理由:即使丢了 15 分钟的数据,对业务影响很小,但能换来极快的备份和恢复体验。

场景 C:数据一致性要求高 (传统做法)

  • 业务特征:购物车数据、即时消息队列、无法从数据库恢复的业务数据。

  • 策略:开启 AOF (everysec),同时也开启 RDB(作为备份)。

  • 注意:Redis 重启时默认加载 AOF,因为数据更全。但 RDB 依然保留用于每天的归档备份。

场景 D:Redis 4.0+ 的“混合持久化”

  • 背景:想要 RDB 的快速启动,又想要 AOF 的数据安全。

  • 原理:在 AOF 重写时,将当前的内存数据做成 RDB 快照放在 AOF 文件的开头,后续的增量操作依然记录为 AOF 日志。

  • 文件结构:[RDB 快照内容] + [AOF 增量日志]

  • 优势:

    1. 重启极快(前半段直接加载 RDB)。

    2. 数据丢失极少(后半段是秒级日志)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 15:40:55

Qt 帮助文档为空问题(查询无结果)

遇到这种问题通常为帮助文档安装不齐全&#xff0c;或安装损坏等&#xff0c;网上有很多去找到那个修复工具修复Qt&#xff0c;但是在Mac里面就非常难找&#xff08;我就没找到&#xff09;所以整理了一个100%有效&#xff0c;无门槛的方法。Qt 查找但是我第一次在上面输入时就…

作者头像 李华
网站建设 2026/4/18 4:43:23

豆包生成带复杂公式的文件如何导出到Word文档

你是否也曾为这样的场景头疼&#xff1a;在豆包 AI 里生成了一份内容详实、公式工整、表格清晰的技术文档或学术回答&#xff0c;满心欢喜地复制到 Word 里准备整理提交时&#xff0c;却发现所有漂亮的公式都变成了一堆无法辨识的乱码&#xff0c;格式也彻底崩塌&#xff1f; …

作者头像 李华
网站建设 2026/4/10 20:32:52

WGD分类进阶--随笔021

基于全基因组复制&#xff08;WGD&#xff09;的 KEGG 功能富集及 Ka/Ks 进化分析 01 分析背景与核心目标 本分析聚焦基因复制事件中全基因组复制&#xff08;WGD&#xff09; 类型&#xff0c;结合 KEGG 功能富集分析解析 WGD 基因的生物学功能特征&#xff0c;通过 Ka/Ks&a…

作者头像 李华
网站建设 2026/4/17 12:54:44

Agentic AI情感智能开发指南:提示工程架构师的需求分析与提示转化

Agentic AI情感智能开发指南&#xff1a;提示工程架构师的需求拆解与提示转化实战 摘要/引言 你有没有过这样的经历&#xff1f; 跟客服AI吐槽“等了一周的快递还没到”&#xff0c;它回“请提供订单号”&#xff1b;用健身APP的AI教练&#xff0c;你说“我好累不想练了”&am…

作者头像 李华
网站建设 2026/3/25 13:23:19

第十四课:Redis 在后端到底扮演什么角色?——缓存模型全景图

在很多后端项目中&#xff0c;你会听到一句话&#xff1a; “加个 Redis 就行了。” 但问题来了—— Redis 到底在后端系统中扮演什么角色&#xff1f;它只是缓存吗&#xff1f; 如果你只把 Redis 当成“加速数据库”的工具&#xff0c;那你只理解了 30%。 Redis 在真实后端系…

作者头像 李华