尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

【Redis性能探秘】数据怎么存、命令怎么执行、宕机了怎么办

【Redis性能探秘】数据怎么存、命令怎么执行、宕机了怎么办
📅 发布时间:2026/7/7 3:33:07

你给项目里引入了Redis,缓存命中率上去了,接口响应时间从200ms降到了5ms。老板满意,你也觉得任务完成了。但有一个问题你可能从来没想过:Redis为什么能这么快?

一个内存操作而已,能有多复杂?如果你这么想,那你就错过了Redis最精彩的部分。这篇文章不打算罗列Redis的常用命令——这些官方文档写得比谁都清楚。我想带你从底层往上看:数据是怎么存的、命令是怎么执行的、宕机了数据怎么办。

读完你会发现,Redis的每一个设计决策,背后都有明确的权衡。

一、数据类型:你看到的是五种,底层是九种

Redis对外宣称提供了五种数据类型:String、List、Hash、Set、ZSet。但如果你去看源码,底层实际有九种数据结构。

这不是Redis在玩数字游戏。它的设计哲学是:对上层暴露统一的API,对下层根据数据特征动态选择最优的存储结构。

1.1 String:SDS比C字符串高明在哪

String是Redis最基础的类型,所有Key都是字符串。但Redis没有直接用C语言的char*,而是自己实现了一个叫SDS(Simple Dynamic String)的结构。

SDS长这样:

struct sdshdr { int len; // 已使用的长度 int free; // 未使用的空间 char buf[]; // 实际字符数组 };

相比C字符串,SDS有三个优势:

O(1)获取长度:C字符串要靠遍历\0才能知道长度,SDS直接读len字段。

空间预分配:修改字符串时,Redis会多分配一些空闲空间。比如你给一个字符串追加内容,SDS会提前把空间备好,下次再追加就不用重新分配内存了。这个优化看似微小,但在高并发写入场景下,减少内存重分配带来的性能提升是量级的。

二进制安全:C字符串用\0判断结尾,存不了图片这类二进制数据。SDS用len判断长度,什么都能存。

String还有三种底层编码:纯数字用int(8字节直接存)、短字符串用embstr(≤44字节,一次内存分配)、长字符串用raw(>44字节,两次内存分配)。Redis连字符串怎么存都在抠细节。

1.2 List:从压缩列表到QuickList的演进

List的底层实现经历过三次迭代。

早期(3.0之前):数据量小时用压缩列表(ZipList)——一块连续的内存,极度节省空间;数据量大时用双向链表——插入删除快,但每个节点要存两个指针,内存开销大。

压缩列表有个致命缺陷叫连锁更新:插入或删除一个节点可能导致后续所有节点重新分配内存,时间复杂度O(N²)。

3.2版本:Redis推出了QuickList——双向链表和压缩列表的混合体。每个链表节点存的是一个压缩列表。既保留了链表的灵活插入,又通过压缩列表节省了内存。

最新版本:压缩列表被ListPack取代,彻底解决了连锁更新问题。

这个演进过程说明了一个道理:没有完美的数据结构,只有适合当前场景的权衡。

1.3 Hash:渐进式Rehash

Hash的底层是哈希表(Dict)。它维护了两个哈希表:ht[0]主表和ht[1]过渡表。

当数据量增长触发扩容时,Redis不会一次性把所有数据从ht[0]搬到ht[1]——那样会阻塞主线程几十毫秒甚至更久。它采用渐进式Rehash:每次增删改查操作时顺带搬几个,分多次搬完。

这个设计极其精妙:把一次性的大开销,分摊到每次操作中,用户几乎感知不到。

1.4 ZSet:跳表为什么比平衡树更讨喜

ZSet(有序集合)的底层是跳表(Skip List)+哈希表。哈希表负责O(1)的成员查找,跳表负责有序的范围查询。

跳表相比平衡树(如红黑树)有三个优势:

  • 实现简单:不用复杂的旋转操作,代码量少一大截

  • 范围查询高效:通过层级结构快速定位区间

  • 并发友好:修改操作影响范围有限

跳表的本质是用空间换时间——多建几层索引,让查找从O(N)变成O(logN)。这个思路在数据库索引中随处可见,但Redis把它用到了极致。

二、单线程:你以为的短板,其实是设计哲学

"Redis是单线程的"——这句话你肯定听过。但如果你以为Redis只有一个线程在干活,那就大错特错了。

2.1 单线程到底指什么?

Redis的"单线程"仅指命令执行核心——处理GET、SET这些客户端请求的线程只有一个。但Redis还有:

  • IO线程:负责网络读写(接收请求、发送响应)

  • 后台进程:负责RDB持久化和AOF重写

  • 后台线程:负责清理过期Key等后台任务

所以准确的说法是:Redis的网络IO和命令执行是单线程的,但持久化等重操作是异步的。

2.2 为什么单线程反而快?

这听起来反直觉——多核时代,单线程怎么比多线程快?

原因一:内存操作,快到离谱。Redis所有数据都在内存里,读写速度是磁盘的10万倍。CPU不是瓶颈,不需要多核来帮忙。

原因二:没有上下文切换。多线程要频繁切换线程状态——保存寄存器、恢复堆栈、调度算法计算。单线程把这些开销全省了。

原因三:没有锁竞争。多线程要操作共享数据结构就得加锁,锁的争用会显著降低性能。单线程压根不需要锁,代码更简单,性能更稳。

原因四:I/O多路复用。Redis通过epoll(Linux)或kqueue(BSD)在同一线程里监听成千上万个客户端连接。事件循环不断调用epoll_wait,哪个连接有数据来了就处理哪个,全程非阻塞。

打个比方:多线程像一家有10个窗口的奶茶店,但每个窗口的店员要频繁切换做不同的事,还要抢原料(锁);Redis像一家只有1个窗口的奶茶店,但原料就在手边(内存),门口有个喇叭喊号(epoll),后厨还有人帮忙备料(后台线程)。10个窗口不一定比1个窗口快。

2.3 单线程的局限性

单线程模型也有短板:耗时操作会阻塞所有请求。

比如KEYS *扫描全量Key、FLUSHALL清空数据库、大Key的删除——这些操作如果直接在主线程执行,会阻塞所有其他请求。

Redis的解决方案:

  • 禁用危险命令:生产环境把KEYS、FLUSHALL重命名或直接禁掉

  • 惰性删除:用UNLINK替代DEL,异步释放内存

  • 大Key拆分:避免单个Key数据量过大

Redis 6.0开始引入了多线程IO,但仅用于网络读写,命令执行依然是单线程。这个设计说明Redis团队很清楚:瓶颈在网络IO,不在命令执行。

三、持久化:内存数据怎么"活"到磁盘上

Redis是内存数据库,重启就什么都没了。持久化就是让数据"活"到磁盘上。

Redis提供了两种持久化方式:RDB和AOF。

3.1 RDB:快照,简单粗暴

RDB在指定时间间隔生成一个内存快照,保存为二进制文件(默认dump.rdb)。

触发方式有两种:

  • 手动:SAVE(阻塞主线程)或BGSAVE(后台子进程)

  • 自动:配置文件里配save 900 1(900秒内至少1个Key变化就触发)

RDB的优点是恢复快——直接加载二进制文件,比一条条重放命令快得多。缺点是可能丢数据——如果两次快照之间宕机,中间的所有修改都丢了。

RDB生成时主进程fork出子进程,子进程和主进程共享同一块内存。利用操作系统的写时复制(Copy-on-Write)机制,主进程修改数据时才会复制内存页。这个设计让RDB对主进程的影响降到了最低。

3.2 AOF:日志,逐条记录

AOF把每个写操作都以文本形式追加到日志文件末尾。重启时逐条重放这些命令,恢复数据。

AOF的刷盘策略由appendfsync控制:

  • always:每次写操作都刷盘——最安全,性能最差

  • everysec(默认):每秒刷一次——性能和安全的平衡

  • no:交给操作系统决定——性能最好,最不安全

AOF的优点是数据更安全(everysec最多丢1秒数据),日志可读性强(就是文本文件)。缺点是文件大、恢复慢。

AOF文件会无限增长,Redis提供了AOF重写(Rewrite)机制——把当前数据状态用最少的命令重新表达一遍,生成一个新的AOF文件,替代旧文件。

3.3 混合持久化:各取所长

RDB恢复快但丢数据多,AOF丢数据少但恢复慢。能不能把两者的优点结合起来?

Redis 4.0给出了答案:混合持久化。

3.3.1 技术实现:RDB格式写入AOF文件

混合持久化的核心机制体现在AOF重写环节。

当开启混合持久化(aof-use-rdb-preamble yes)并触发AOF重写时,Redis会fork一个子进程。子进程在生成新AOF文件时,不再像传统AOF重写那样把数据转换成RESP命令写入,而是直接将当前内存数据以RDB二进制格式写入AOF文件头部。

用代码来理解会更直观:

int rewriteAppendOnlyFile(char *filename) { if (server.aof_use_rdb_preamble) { // 混合模式:以RDB格式保存全量数据 rdbSaveRio(&aof, &error, RDB_SAVE_AOF_PREAMBLE, NULL); } else { // 传统模式:以RESP命令格式写入 rewriteAppendOnlyFileRio(&aof); } // 后续追加增量命令... }

RDB部分写入完成后,重写期间新执行的Redis命令,继续以AOF格式追加到文件末尾。

最终生成的AOF文件结构是:[RDB Header][RDB Data][AOF Commands]。

新旧文件切换也有讲究:新文件一开始不叫appendonly.aof,等到重写完成才会改名,覆盖原有AOF文件。

3.3.2 恢复流程:RDB快速加载 + AOF增量重放

开启混合持久化后,Redis重启时的恢复流程变成两步:

  1. 加载RDB部分:直接加载AOF文件头部的RDB二进制数据,快速恢复全量数据

  2. 重放AOF部分:再重放文件末尾的增量AOF命令,补全重写期间的新修改

相比传统AOF需要逐条重放所有历史命令,混合模式只需重放两次重写之间的增量命令,恢复效率大幅提升。

3.3.3 如何开启

开启混合持久化需要满足两个条件:

  1. Redis版本 ≥ 4.0

  2. 同时开启AOF(appendonly yes)和混合模式(aof-use-rdb-preamble yes)

>注意:Redis 4.0中该功能默认关闭,5.0版本开始默认开启。

3.3.4 混合持久化的优势

对比维度

纯RDB

纯AOF

混合持久化

恢复速度

快

慢(需重放所有命令)

快(RDB快速加载+少量增量重放)

数据安全

差(可能丢大量数据)

好(取决于fsync策略)

好(继承AOF的刷盘策略)

文件大小

小(二进制压缩)

大(文本命令)

中等(RDB压缩+AOF增量)

适用场景

备份、灾备

强数据安全

生产环境通用

混合持久化结合了RDB的快速恢复和AOF的按写持久化能力,在恢复速度和数据安全性之间找到了最佳平衡点。

3.3.5 注意事项

RDB文件依然存在:即使开启了混合持久化,同时配置了RDB的save参数,Redis仍然会生成独立的RDB文件,并不会被省略。两者是并行工作的。

兼容性问题:混合持久化生成的AOF文件,旧版本Redis无法读取。如果需要降级,要先把混合模式关掉,重新生成纯AOF文件。

重写期间的管道机制:子进程在AOF重写时,会通过管道从父进程持续读取增量数据并缓存下来。即使在以RDB格式保存全量数据的过程中,也会同时处理管道数据,不会造成管道阻塞。

3.4 生产环境怎么选?

场景

推荐方案

理由

纯缓存场景(数据丢了可从DB重建)

不持久化 或 仅RDB

追求极致性能,数据丢失可接受

一般业务缓存(用户信息、会话等)

RDB + AOF everysec

平衡性能和安全,最多丢1秒数据

核心业务数据(交易状态、配置等)

RDB + AOF always

数据安全优先,性能代价可接受

极致数据安全(金融级)

RDB + AOF always + 定期备份

多重保障,写入性能最差

四、写在最后:Redis的设计哲学

回到开头的那个问题:Redis为什么能这么快?

现在你可以回答得更完整了:

  • 数据结构层面:SDS省去了strlen的遍历、空间预分配减少了内存重分配、跳表让范围查询O(logN)、渐进式Rehash把大搬迁拆成了小操作

  • 线程模型层面:单线程消除了锁竞争和上下文切换、epoll让单线程能管上万连接、非阻塞IO让线程永远不会"干等"

  • 持久化层面:fork+写时复制让RDB几乎不阻塞主线程、AOF每秒刷盘平衡了性能和安全

Redis的每一个设计,都在回答同一个问题:如何在保证功能的前提下,把性能压榨到极致?

它没有用最复杂的技术,而是用了最合适的技术。这大概就是Redis最大的启发:高性能不是堆砌复杂,而是做对每一次权衡。

最后,留几个问题供你思考:

  • Redis的过期删除策略为什么选用"定期删除+惰性删除"的组合,而不是一个定时器?

  • 为什么Redis的跳表实现中,每个节点的层高是随机生成的,而不是固定的?

  • AOF重写时,主进程也在处理写入请求,新AOF文件如何保证包含了重写期间的新数据?

如果你能回答出这些问题,说明你已经真正理解了Redis的设计思路。

延伸阅读

  • Redis源码:github.com/redis/redis(源码注释清晰,值得一读)

  • Redis设计与实现(黄健宏著):Redis源码分析的经典读物

  • Redis官方文档:redis.io/docs(可能是最好的官方文档之一)

相关新闻

  • mysql数据库服务器的启动方式(默认开机自启)
  • Harness Engineering与Hermes Agent企业级AI编程实战指南
  • FlipDrop:一个面向本地商家的盲盒 + 秒杀 O2O 平台

最新新闻

  • 借助 Semantic Kernel 与 Blazor 构建智能交互式前端应用
  • 一段15秒短视频催生的小程序:我是车标王从0到2.0的创作手记
  • Kubernetes 事件驱动:Informer 机制不只是给控制器用
  • 3类常见舵机(模拟/数字/总线)控制原理对比:PWM、响应速度与精度实测
  • 2026年标签打印软件十大品牌排行榜,谁更强?
  • Sneakerweb 协议:无需许可,用物理介质实现网页点对点发布与分享!

日新闻

  • Android逆向分析全能助手:集成化工具链与自动化工作流设计
  • 面搜索(Faceted Search)原理与工程实践指南
  • 神经网络调参避坑指南:从5个常见Loss曲线形态定位超参数问题

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号