🔥个人主页: 中草药
🔥专栏:【中间件】企业级中间件剖析
一、持久化
对于数据的存储而言,为保证速度快,数据得在内存中,但为了持久,数据还得存在硬盘之中,Redis 的持久化机制是其高可靠性的核心,主要用于在重启或崩溃后恢复数据。它提供两种主要持久化方式:RDB(Redis Database) 和 AOF(Append-Only File),以及从 4.0 版本开始支持的 混合持久化。
RDB(Redis Database 快照持久化)
工作原理
基于快照:RDB 通过定期生成内存数据的二进制快照(Snapshot)保存到磁盘(默认文件为
dump.rdb
)。触发机制:
手动触发:通过
SAVE
(执行时,Redis会阻塞主线程,可能会导致keys *的效果,一般不建议使用)BGSAVE
(BackGround ,非阻塞,Redis通过后台子进程处理)命令。自动触发:根据配置文件中的
save
规则(如save 900 1
表示 900 秒内至少 1 个键被修改时触发)。(生成快照是一个成本比较高的操作,不能让这个操作频繁执行)
自动触发除此之外有以下这几种方式:
1、正常流程重新启动redis服务器,通过shutdown命令(redis里的一个命令)关闭服务器,也会触发(service redis-server restart),但异常重启(kill -9 或 服务器掉电)来不及生成RDB,会出现数据丢失
2、redis进行主从复制的时候,主节点也会自动生成RDB快照,然后把rdb文件内容传输给节点
bgsave:使用
BGSAVE
时,Redis 首先会判定当前是否存在其他正在工作的子进程,如果没有主进程会 fork 一个子进程,子进程负责将内存数据写入临时 RDB 文件生成快照,父进程将继续接受客户端的请求,继续正常提供服务,子进程完成整体的持久化过程后替换旧文件,并通知父进程,父进程更新一些统计信息,子进程结束销毁
fork
Linux 的
fork()
是创建新进程的核心系统调用,它会创建一个与父进程几乎完全相同的子进程,包括代码段、数据段、堆栈、文件描述符、信号处理等。子进程拥有独立的进程空间,但初始时与父进程共享物理内存(通过写时复制,Copy-On-Write, COW 技术优化性能)。
redis生成的rdb文件,是存放在 redis 的工作目录之中,可在 redis 的工作目录
rdb的镜像文件
redis提供了检查rdb文件的工具
优点
紧凑高效:RDB是一个紧凑压缩的二进制文件,体积小,适合备份、全量复制和灾难恢复。
快速恢复:加载 RDB 文件的速度远快于 AOF。
低性能影响:生成快照由子进程处理,主进程几乎不受影响(除 fork 时的短暂延迟)。
缺点
数据丢失风险:若两次快照间发生故障,会丢失最后一次快照后的数据。
大内存开销:RDB无法做到实时持久化,秒级持久化,fork 子进程时,属于重量级操作,若数据集过大,可能导致主进程短暂阻塞(尤其在虚拟内存不足时)。
兼容性风险:RDB文件使用特定的二进制格式保存,Redis版本演进过程中有过多个RDB版本,兼容性存在风险
配置示例
修改配置后,需要重新启动服务器 service redis-server restart
save 900 1 # 900秒内至少1个键修改则触发
save 300 10 # 300秒内至少10个键修改
save 60 10000 # 60秒内至少10000个键修改
save "" # 关闭自动生成快照
stop-writes-on-bgsave-error yes # 快照失败时停止写入
rdbcompression yes # 压缩RDB文件
rdbchecksum yes # 校验数据完整性
rdb文件时二进制的,如果redis在启动时使用这个文件,造成的后果是不可预期的,可能redis的服务器可以启动,得到的数据可能正确,也可能有问题,也有更大的可能redis服务器直接启动失败。这时我们需要用到redis提供的检查rdb文件的工具redis-check-rdb
AOF(Append Only File 日志追加持久化)
工作原理
AOF默认一般是关闭状态,当AOF启动的时候,redis不再读取 RDB 的内容,手动修改配置文件打开
引入AOF操作之后,即使既要写内存也要写硬盘,也并没有影响到redis处理请求的速度
1、AOF机制并非直接让工作线程把数据写入硬盘,而是先写入一个内存中的缓冲区(因此,如果缓冲区没来得及写入硬盘,也会发生数据丢失),积累一波后,再统一写入硬盘之中,降低写硬盘的次数
2、AOF append是每次把新的操作写入原有文件的末尾,属于顺序写入,在硬盘上读写数据,顺序读写速度相对较快,随机访问的速度比较慢
记录写操作:以文本日志形式记录所有写命令(如 SET
、DEL
),保存到 appendonly.aof
文件。
同步策略:
频率越高,数据可靠性越高,性能越低
always:每次写命令都同步到磁盘(最安全,性能最低)。
everysec:每秒同步一次(默认,平衡安全与性能)。
no:由操作系统决定同步时机(性能最高,风险最大)。
AOF 重写机制(Rewrite):
解决 AOF 文件膨胀问题。通过生成新 AOF 文件,删除冗余命令(如多次修改同一键,仅保留最终状态)到达给文件瘦身的目的。
触发方式:手动执行
BGREWRITEAOF
或根据配置自动触发如auto-aof-rewrite-percentage 100
表示文件增长 100% 时触发auto-aof-rewrite-min-size 64mb
表示触发重写时AOF最小文件大小)。重写流程:重写时,不关心aof文件原本有什么,只关心内存中最终的数据状态(此时的状态已经相当于AOF文件结果整理后的摸样)再fork之后,父进程会把新来的请求同时写入两个缓冲区,其中的aof_rewrite_buf缓冲区,专门放fork之后的数据,子进程这边,把aof数据写完之后,会通过信号通知一下进程,父进程会把aof_rewrite_buf缓冲区的内容写进新的AOF文件(父进程同时往旧的AOF文件写数据是因为考虑到极端情况,保证数据的完整性)
如果在执行
BGREWRITEAOF
操作时,发现redis已经在进行AOF重写了,此时不会再执行操作而是直接返回,而如果此时redis正在生成RDB快照,aof重写操作就会等待,等待RDB快照生成完毕之后,再执行AOF重写
优点
数据更安全:最多丢失 1 秒数据(默认
everysec
策略)。可读性强:AOF 文件为文本格式,便于人工分析。
灵活恢复:支持修复损坏的 AOF 文件(如使用
redis-check-aof
工具)。
缺点
文件体积大:AOF 文件通常比 RDB 大。
恢复速度慢:重放所有命令恢复数据,耗时长。
写性能开销:高频写入场景下,AOF 可能影响吞吐量。
当redis上同时存在aof文件和rdb快照时,以rdb快照为主
混合持久化(Redis 4.0+)
工作原理
结合 RDB 和 AOF:在 AOF 重写时,新生成的 AOF 文件前半部分是 RDB 格式的快照数据,后半部分是增量 AOF 命令。
恢复流程:先加载 RDB 快照,再执行后续 AOF 命令,兼顾速度和数据完整性。
配置
aof-use-rdb-preamble yes # 启用混合持久化(需同时启用AOF)
优点
快速恢复:RDB 快照加速数据加载。
低数据丢失:AOF 记录增量命令,保障数据安全。
二、事务
Redis的事务机制提供了一种将多个命令打包并按顺序执行的途径,但其实现与传统关系型数据库的事务(如ACID特性)有显著差异。以下是对Redis事务的详细解析:
与传统数据库事务的对比
特性 | Redis事务 | 传统数据库(如MySQL) |
---|---|---|
原子性 | 部分保证(运行时错误不中断) | 完全保证(ACID) |
隔离性 | 不涉及隔离性,单线程保证指令操作串行化执行 | 支持多种隔离级别(如读提交、可重复读) |
回滚机制 | 不支持 | 支持通过ROLLBACK回滚 |
错误处理 | 分语法错误(拒绝执行)和运行时错误(继续执行) | 事务内错误触发回滚 |
原子性:Redis事务的原子性较弱。命令队列在EXEC
时整体执行(中途不会穿插其他命令),但运行时错误不会回滚。
隔离性:由于Redis单线程模型,事务执行期间不会被其他命令打断,具备隔离性(类似可串行化)。
无回滚机制:设计哲学追求简单高效,故不支持回滚,需开发者自行处理错误。
提到Redis的原子性是存在争议的,由于MySQL存在回滚,他能保证多个操作打包在一起,要不全都执行成功,要么全都不执行,当事务中存在操作执行失败,进行回滚。而Redis只能保证将多个操作按事务的方式执行,如果出现失败也不会发生回滚的操作。所以存在说法Redis不具备原子性
基本命令
Redis事务通过以下命令管理:
MULTI:开启事务,之后的命令被放入队列,直到执行EXEC或DISCARD。
EXEC:执行事务队列中的所有命令。
DISCARD:取消事务,清空队列中的命令。(若开启事务,发生服务器的重启,效果等同于discard)
WATCH:监视一个或多个键,用于实现乐观锁(CAS操作)。
UNWATCH:取消对所有键的监视。
示例流程:
> WATCH key1 # 监视键key1
> MULTI # 开启事务
> SET key1 value1 # 命令入队
> GET key1 # 命令入队
> EXEC # 执行事务
若在WATCH
后EXEC
前,key1
被其他客户端修改,事务将失败,返回(nil)
。
WATCH与乐观锁
作用:
WATCH
监视键,若事务执行前这些键被修改,事务将失败。需结合重试机制实现乐观锁。典型应用场景:余额扣减。
乐观锁预期锁冲突概率低,假设在大多数情况下,并发操作不会发生冲突,只有在更新数据时才去检查是否有冲突。就好像它总是乐观地认为大家都能 “和平共处”,不会出现数据争抢的情况。
客户端调用 WATCH
:
例如
WATCH balance
,客户端将balance
的当前版本号保存到自己的监视列表中。Redis 服务端会将该客户端加入
balance
键的监视者列表(一个链表结构,记录所有监视该键的客户端)。
客户端开启事务(MULTI
):
后续所有命令会缓存在事务队列中,但不会立即执行。
其他客户端的操作:
如果在事务提交前,其他客户端修改了
balance
(如SET balance 100
),balance
的版本号会递增。Redis 会遍历
balance
的监视者列表,标记所有监视该键的客户端的事务为失效(即事务提交时会失败)。
客户端提交事务(EXEC
):
校验阶段:Redis 检查该客户端所有
WATCH
的键的当前版本号是否与最初记录的版本号一致。一致:执行事务队列中的命令,更新键的版本号,并返回所有命令的结果。
不一致:直接放弃事务,返回
nil
(事务失败)。
这样的设定思想有点类似于CAS里面的ABA问题
烈火试真金,逆境试强者。--塞内加
🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀
以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐
制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸