- [1. 渐进式遍历进式遍历](#1. 渐进式遍历进式遍历)
- [2. Redis 数据库管理命令](#2. Redis 数据库管理命令)
- [3. 持久化](#3. 持久化)
- [3.1 RDB](#3.1 RDB)
- [3.1.1 RDB 触发机制](#3.1.1 RDB 触发机制)
- [3.1.2 RDB 文件的处理](#3.1.2 RDB 文件的处理)
- [3.1.3 RDB 的特点](#3.1.3 RDB 的特点)
- [3.2 AOF](#3.2 AOF)
- [3.2.1 AOF 工作流程](#3.2.1 AOF 工作流程)
- [3.2.2 命令写入](#3.2.2 命令写入)
- [3.2.3 AOF 文件同步策略](#3.2.3 AOF 文件同步策略)
- [3.2.4 AOF 文件的重写机制](#3.2.4 AOF 文件的重写机制)
- [3.2.5 启动时数据恢复](#3.2.5 启动时数据恢复)
- [3.1 RDB](#3.1 RDB)
- [4. Redis 事务](#4. Redis 事务)
- [4.1 什么是事务](#4.1 什么是事务)
- [4.2 事务操作](#4.2 事务操作)
- [5. 小结](#5. 小结)
- [6. 经典面试题](#6. 经典面试题)
✅️ 1.渐进式遍历
Redis使用scan命令进行渐进式遍历键,进而解决直接使用keys获取键值时可能出现的阻塞问题。每次执行scan命令的时间复杂度是O(1),但是要完整的遍历完成所有键的遍历,需要执行多次scan.
📌 注意
首次执行
scan命令从0开始。当
scan返回的下次位置为0时,遍历结束。渐进式遍历命令其实就是一组命令 ,而不是一条命令。
语法 :SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
📚 理解:
cursor光标不能简单的理解成为"下标",不是一个连续递增的整数,仅仅是一个字符串,光标这个概念,程序员、客户端是无法理解的,只有Redis服务器内部知道这个光标对应的位置。pattern表示匹配模式count代表当前扫描的个数- 此处的
count只是给Redis服务器一个提示或者建议,写入的count和实际返回的key的个数不一定完全是相同的,但是不会相差很多。- 光标设置为
0,表示当前遍历是从头开始的- 光标的返回值表示,下次遍历时,光标从哪里开始。
- 这里的渐进式遍历,在遍历的过程中,不会在服务器这边存储任何的状态信息,此处的的遍历随时可以终止 -- 不会对服务器产生任何的副作用。
- 尽管使用
scan命令获取key比直接使用keys *要好点,但是使用的时候还是有很多的注意事项,有很多出错的可能性。- 渐进性遍历
scan虽然解决了阻塞的问题,但如果在遍历期间键有所变化(增加、修改、删除),可能导致遍历时键的重复遍历或者遗漏,这点务必在实际开发中考虑。
示例
bash
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
2. Redis 数据库管理命令
Redis提供了几个针对于Redis数据库的命令:dbsize select flushdb flushall.

3. 持久化
❓问题引入
Redis是一个内存级数据库,将数据放到内存中,如果进程异常退出或者断电,会导致数据的丢失,所以想要持久化,就需要将内存中的数据存放的奥磁盘中进行备份。对于Redis,使用RBD(Redis DataBase)和AOF(Append Only File)两种持久化机制来保证数据的持久性。
3.1 RDB
RDB持久化是把当前进程数据生成快照保存到硬盘中的过程 ,触发RDB持久化过程分为手动触发 和自动触发。
3.1.1 RDB触发机制
① 手动触发
save命令:执行当前命令时会阻塞当前Redis服务器主进程,知道RDB持久化过程完成,对于内存使用量比较大的示例会造成长时间阻塞,所以这个命令基本不使用。- bgsave命令:Redis进程执行fork操作 创建子进程,
RDB持久化任务由这个子进程来完成,完成后自动结束。阻塞一般发生在fork阶段,时间很短,影响很小。所以Redis内部的所有涉及RDB的操作都采用类似bgsave的方式。
下图是使用bgsave命令完成RDB持久化原理图:

如果执行的是
save命令,此时是不会触发子进程、文件替换逻辑的。如果是
save就直接在主进程中,网刚才的同一个文件中写入数据了。
② 自动触发 - 实战中最常用的几种方式
- 使用
save配置,如"save m n "表示m秒内数据集发生了n次修改,自动进行RDB持久化。 - 从节点进行全量复制时,主节点自动进行
RDB持久化,然后将RDB文件内容发送给从节点。 - 执行
shutdown命令关闭Redis时,执行RDB持久化。
3.1.2 RDB文件的处理
保存 :RDB ⽂件保存再 dir 配置指定的⽬录(默认 /var/lib/redis/)下,⽂件名通过 dbfilename配置(默认 dump.rdb)指定。可以通过执⾏config set dir {newDir}和 config set dbfilename {newFilename} 运⾏期间动态执⾏,当下次运⾏时RDB⽂件会保存到新⽬录。
压缩 :Redis 默认采⽤LZF算法对⽣成的 RDB ⽂件做压缩处理,压缩后的⽂件远远⼩于内存⼤⼩,默认开启,可以通过参数 config set rdbcompression {yes|no} 动态修改。
校验 :如果Redis启动时加载到损坏的 RDB ⽂件会拒绝启动。这时可以使⽤ Redis 提供的redis-check-dump ⼯具检测 RDB ⽂件并获取对应的错误报告。
💡 1. 虽然压缩
RDB会消耗CPU资源,但可以⼤幅降低⽂件的体积,⽅便保存到硬盘或通过⽹络发送到从节点,因此建议开启。
RDB持久化操作是可以执行多次的
当执行生成
rdb镜像操作的时候,此时就会把要生成的快照数据,先保存到一个临时文件中,当这个快照数据生成完毕以后,再删除之前的rdb文件,把新生成的临时的rdb文件名字改成刚才的dump.rdb.对于
redis来说,修改配置文件后,需要重启一下服务器才能生效。
3.1.3 RDB的特点
RDB是⼀个紧凑压缩的⼆进制⽂件,代表Redis在某个时间点上的数据快照。⾮常适⽤于备份,全量复制等场景。⽐如每6⼩时执⾏bgsave备份,并把RDB⽂件复制到远程机器或者⽂件系统中(如hdfs)⽤于灾备。Redis加载RDB恢复数据远远快于AOF的⽅式。RDB⽅式数据没办法做到实时持久化 / 秒级持久化。因为bgsave每次运⾏都要执⾏fork创建⼦进程,属于重量级操作,频繁执⾏成本过⾼。RDB⽂件使⽤特定⼆进制格式保存,Redis版本演进过程中有多个RDB版本,兼容性可能有⻛险。
3.2 AOF
3.2.1 AOF 工作流程
AOF(Append Only File)持久化:以独⽴⽇志的⽅式记录每次写命令,重启时再重新执⾏ AOF⽂件中的命令达到恢复数据的⽬的。AOF 的主要作⽤是解决了数据持久化的实时性,⽬前已经是Redis 持久化的主流⽅式。
开启 AOF 功能需要设置配置:appendonly yes,默认不开启。AOF ⽂件名通过appendfilename 配置(默认是 appendonly.aof)设置。保存⽬录同 RDB 持久化⽅式⼀致,通过 dir配置指定。AOF 的⼯作流程操作:命令写⼊(append)、⽂件同步(sync)、⽂件重写(rewrite)、重启加载(load)。
下图是AOF的工作流程图:

3.2.2 命令写入
AOF 命令写⼊的内容直接是⽂本协议格式。例如 set hello world 这条命令,在 AOF 缓冲区会追加如下⽂本:
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
此处遵守 Redis 格式协议,Redis 选择⽂本协议可能的原因:⽂本协议具备较好的兼容性;实现简单;具备可读性。
AOF 过程中为什么需要 aof_buf 这个缓冲区? Redis 使⽤单线程响应命令,如果每次写 AOF ⽂件都直接同步硬盘,性能从内存的读写变成 IO 读写,必然会下降。先写⼊缓冲区可以有效减少 IO 次数,同时,Redis 还可以提供多种缓冲区同步策略,让⽤⼾根据⾃⼰的需求做出合理的平衡。
3.2.3 AOF文件同步策略
3.2.4 AOF文件的重写机制

❓️ 拓展问题1 :子进程里面的内存数据是父进程
fork之前的状态,fork之后,新来的请求,对内存造成的修改,是子进程不知道的。解决方案 :子进程把
aof文件数据写完之后,会通过信号通知一下父进程,父进程再将aof_rewrite_buf缓冲区中的内容写到新AOF文件中,就可以用新的AOF文件代替旧的AOF文件。拓展问题2 :父进程
fork完毕之后,就已经让子进程写新的aof文件了,而且随着时间的推移,子进程很快就写完了新的文件,要让新的aof文件替代旧的~~ 父进程此时还在继续写这个将亡的旧的aof文件是否还有意义?{不能不写}原因 :考虑到极端情况,假设再重写过程中,服务器挂了,子进程内存的数据就会丢失,新的
aof文件内容还不完整,所以如果父进程不坚持写旧aof文件,重启旧没法保证数据的完整性了。
🐳 理解:
- 重写的时候,并不关心
aof文件中原来都有什么内容,只是关心内存中最终的数据状态即可。子进程只需要把当前内存中的数据读取出来,以AOF的文件格式写入到一个全新的AOF文件中。- 内存中的数据的状态,就已经相当于是把
AOF文件结果整理后的模样了。
📌 结论:因此Redis就存在一种机制,能够针对文件进行整理操作,这个整理就是能够剔除其中的冗余操作,并且合并一些操作,达到给AOF文件 瘦身 的效果。
3.2.5 启动时数据恢复
当 Redis 启动时,会根据RDB和 AOF ⽂件的内容,进⾏数据恢复。

混合持久化:结合
rdb和aof的特点~~
AOF本来是按照文本的方式来写入文件的,但是文本的方式写文件,后续加载的成本是非常高的.此时
Redis就引入了"混合持久化"的方式-- 结合了rdb和aof的特点按照
aof的方式,每一个请求和操作都记录入文件,在触发重写之后,就会把当前内存的状态按照按照rdb的二进制格式写入到新的aof文件中,后续再进行的操作,仍然是按照aof文本的方式追加到文件
4. Redis 事务
4.1 什么是事务

4.2 事务操作
MULTI 开启一个事务,执行成功后返回OK,EXEC表示执行事务,两者配合使用。
示例:
bash
127.0.0.1:6379> MULTI # 开启一个事务
OK
127.0.0.1:6379> set key 100
QUEUED
127.0.0.1:6379> set key 200
QUEUED
127.0.0.1:6379> set key 300
QUEUED
127.0.0.1:6379> EXEC # 执行一个事务
1) OK
2) OK
3) OK
127.0.0.1:6379> get key
"300"
📌 说明:每次在事务中添加一个操作,都会提示一个"QUEUED",说明命令已经进入到客户端的事务队列中,当执行EXEC命令时,客户端才会将事务队列中的命令发送到服务器中。
DISCARD 放弃当前事务,此时会直接清空事务队列,之前的操作都不会被执行。
示例:
bash
127.0.0.1:6379> multi # 开启一个事务
OK
127.0.0.1:6379> set fruit apple
QUEUED
127.0.0.1:6379> set fruit pear
QUEUED
127.0.0.1:6379> discard # 放弃一个事务
OK
127.0.0.1:6379> get fruit # 由于事务队列中的命令没有执行,导致fruit中为空
(nil)
在执行事务的过程中,如果某个事务中正在修改的值被其他的客户端修改了,此时就容易出现数据不一致的问题。watch 命令就是⽤来解决这个问题的. watch 在该客⼾端上监控⼀组具体的 key.
- 当开启事务的时候, 如果对
watch的key进⾏修改, 就会记录当前key的 "版本号". (版本号是个简单的整数, 每次修改都会使版本变⼤. 服务器来维护每个key的版本号情况) - 在真正提交事务的时候, 如果发现当前服务器上的
key的版本号已经超过了事务开始时的版本号, 就会让事务执⾏失败. (事务中的所有操作都不执⾏).
示例:
bash
## 客户端1执行过程
127.0.0.1:6379> watch k1 # 开始监控 k1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 100 # 进⾏修改, 从服务器获取 k1 的版本号是 0. 记录 k1 的版本号. (还没真修改呢, 版本号不变)
QUEUED
127.0.0.1:6379> set k2 1000
QUEUED
## 客户端2执行过程
127.0.0.1:6379> set k1 200 # 修改成功, 使服务器端的 k1 的版本号 0 -> 1
OK
## 客户端执行过程
127.0.0.1:6379> EXEC # 真正执⾏修改操作, 此时对⽐版本发现, 客⼾端的 k1 的版本号是 0, 服务器上的版本号是 1, 版本不⼀致! 说明有其他客⼾端在事务中间修改了 k1 !!!
(nil)
127.0.0.1:6379> get k1
"200"
127.0.0.1:6379> get k2
(nil)
# 此时说明事务已经被取消了. 这次提交的所有命令都没有执⾏.
# unwatch相当于watch命令的逆操作
🏠 扩展知识
watch的实现就类似于一个"乐观锁"。乐观锁和悲观锁不是指某个具体的锁,而是指的是某一类锁的特性
乐观锁:加锁之前,就有一个心理预期,预期接下来的冲突的概率比较低。
悲观锁:加锁之前,就有一个心理预期,接下来锁冲突的概率比较大。
watch的实现就相当于基于版本号来实现的"乐观锁"。
✅️ 5.小结
本文围绕 Redis 的高阶用法与可靠性保障,梳理了四个核心知识点:
- 渐进式遍历 :
scan命令以O(1)的单次复杂度分批遍历键,避免了keys *造成的阻塞;但遍历期间键的增删改可能导致重复或遗漏,且count只是建议值,cursor为0表示遍历结束。 - 数据库管理命令 :
dbsize、select、flushdb、flushall用于查看键数量、切换库以及清空数据,其中flushall会清空所有库,需谨慎使用。 - 持久化机制 :
RDB以二进制快照形式保存某一时刻的全量数据,文件紧凑、恢复快,适合备份与全量复制,但无法做到实时持久化;AOF以文本日志方式记录每次写命令,实时性更好,通过aof_buf缓冲与同步策略在性能与安全间平衡,并借助重写机制剔除冗余命令实现"瘦身";- 混合持久化结合两者优势,重写时以
RDB二进制格式写入当前内存状态,后续操作继续以AOF文本追加。
- Redis 事务 :
MULTI开启、EXEC提交、DISCARD放弃,命令先入客户端事务队列;WATCH基于版本号实现乐观锁,在提交时校验键是否被其他客户端修改,从而保证数据一致性。
一句话总结:用 scan 替代 keys 保证遍历安全,用 RDB + AOF 保证数据不丢,用 MULTI/EXEC/WATCH 保证操作原子与一致。
✅️ 6.经典面试题
Q1:keys * 和 scan 有什么区别?为什么生产环境推荐用 scan?
keys * 会一次性遍历所有键,时间复杂度为 O(N),在键数量庞大时会长时间阻塞 Redis 单线程;scan 采用渐进式遍历,每次执行复杂度为 O(1),通过游标分批返回结果,不会阻塞服务器。但 scan 在遍历期间若键发生变化,可能出现重复或遗漏,且 count 只是提示值,实际返回数量不固定。
Q2:RDB 的 save 和 bgsave 有什么区别?
save 在主进程中同步执行,会阻塞整个 Redis 服务器直到持久化完成,内存数据量大时阻塞时间很长,基本不推荐使用;bgsave 通过 fork 创建子进程异步完成持久化,仅在 fork 阶段短暂阻塞主进程,影响很小,是 Redis 内部 RDB 操作的主要方式。
Q3:RDB 和 AOF 各自的优缺点是什么?如何选择?
RDB 是紧凑的二进制快照,文件体积小、恢复速度快,适合备份和全量复制;但无法做到秒级持久化,fork 成本高,且不同版本间存在兼容性风险。AOF 以文本记录写命令,实时性好、可读性强,但文件体积大、恢复速度慢。实际生产中通常两者结合,或采用混合持久化:以 RDB 格式保存基线数据,以 AOF 追加增量操作。
Q4:AOF 为什么需要 aof_buf 缓冲区?同步策略有哪些?
Redis 是单线程响应命令,若每次写 AOF 都直接同步磁盘,性能会从内存读写退化为 IO 读写。引入 aof_buf 可以先写缓冲区,减少 IO 次数,并提供多种同步策略(如 always、everysec、no),让用户在性能与数据安全之间做平衡。
Q5:AOF 重写时,父进程为什么还要继续写旧的 AOF 文件?
重写由子进程基于 fork 时刻的内存快照生成新 AOF 文件,期间新请求的修改子进程并不知道,父进程会将其写入 aof_rewrite_buf,待子进程完成后合并到新文件。父进程继续写旧 AOF 是为了兜底:若重写过程中服务器宕机,新文件尚不完整,旧文件仍能保证数据完整性。
Q6:Redis 事务和关系型数据库事务有什么区别?
Redis 事务通过 MULTI 将命令入队、EXEC 一次性提交执行,具备一定的原子性,但不支持回滚 :若某条命令运行时出错,其余命令仍会执行。它也不支持隔离级别,只能通过 WATCH 基于版本号实现乐观锁,在提交时检测键是否被修改,若被修改则整个事务放弃执行。
Q7:WATCH 的实现原理是什么?它属于乐观锁还是悲观锁?
WATCH 会记录被监控键的版本号,EXEC 提交时对比服务器上键的当前版本号,若不一致说明事务期间有其他客户端修改了该键,事务整体不执行。它属于乐观锁:假设冲突概率较低,不加锁而是提交时校验,冲突则重试,避免了悲观锁长时间持有锁带来的开销。