[转帖]深入理解Redis的持久化

深入,理解,redis,持久 · 浏览次数 : 0

小编点评

**AOF文件替换老文件的步骤:** 1. **创建伪客户端:**由于Redis的命令只能在客户端上下文中执行,所以创建一个与客户端连接的伪客户端,作为AOF刷写操作的执行器。 2. **从AOF文件中分析并读取一条命令:**伪客户端读取AOF文件中的首条命令,并将其解码为字节序列。 3. **使用伪客户端执行该命令:**使用伪客户端执行执行该命令的字节序列。 4. **重复步骤 2 和 3,直到AOF文件中的所有命令都被处理完:**伪客户端重复执行步骤 2 和 3,直到AOF文件中的所有命令执行完毕。 5. **注意 AOF的持久化也可能会造成阻塞:**AOF常用的持久化策略是everysec,在这种策略下,fsync同步文件操作由专门线程每秒调用一次。当系统磁盘较忙时,会造成Redis主线程阻塞。 6. **AOF的相关变量解释:**这些变量用于记录AOF文件的处理状态,例如最近一次同步时间、当前大小、基础大小、 pending rewrite 等。 7. **开启AOF:**如果开启了AOF,还会增加一些变量用于记录AOF刷写操作的进度。 8. **每秒执行一次同步磁盘操作:**AOF线程负责每秒执行一次同步磁盘操作,并记录最近一次同步时间。 9. **主线程负责对比上次AOF同步时间:**每隔一段时间,主线程会对比上次AOF同步时间和现在的时间,如果距上次同步成功时间在2s内,直接返回。如果超过2s,主线程会阻塞,直到同步操作完成。 10. **使用everysec策略:**最多会丢失2s数据,而不是1s,因为everysec策略每秒调用一次fsync操作,可以保证AOF刷写操作在系统磁盘速度允许的情况下进行。

正文

https://www.cnblogs.com/ivictor/p/9749465.html

 

RDB

RDB是将当前数据生成快照保存到硬盘上。

 

RDB的工作流程:

1. 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。

2. 父进程执行fork操作创建子进程,fork操作过程中父进程被阻塞。

3. 父进程fork完成后,bgsave命令返回“* Background saving started by pid xxx”信息,并不再阻塞父进程,可以继续响应其他命令。

4. 父进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。根据lastsave命令可以获取最近一次生成RDB的时间,对应info Persistence中的rdb_last_save_time。

5. 进程发送信号给父进程表示完胜,父进程更新统计信息。

 

对于大多数操作系统来说,fork都是个重量级操作,虽然创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。

子进程通过fork操作产生,占用内存大小等同于父进程,理论上需要两倍的内存来完成持久化操作,但Linux有写时复制机制(copy-on-write)。父子进程会共享相同的物理内存页,当父进程处理写请求时会把要修改的页创建副本,而子进程在fork操作过程中会共享父进程的内存快照。

 

触发机制:

1. 手动触发

   包括save和bgsave命令。

    因为save会阻塞当前Redis节点,所以,Redis内部所有涉及RDB持久化的的操作都通过bgsave方式,save方式已废弃。

2. 自动触发

    1> 使用save的相关配置。

    2> 从节点执行全量复制操作。

    3> 执行debug reload命令。

    4> 执行shutdown命令时,如果没有开启AOF持久化功能则会自动执行bgsave。

 

RDB的优缺点:

优点:

1. RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照,适合备份,全量复制等场景。

2. 加载RDB恢复数据远远快于AOF的方式。

缺点:

没办法做到实时持久化/秒级持久化,因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。

 

RDB的相关参数

复制代码
save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes

rdbcompression yes

rdbchecksum yes

dbfilename dump.rdb

dir ./
复制代码

 

其中,前三个参数的含义是,

#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed

 

如果要禁用RDB的自动触发,可注销这三个参数,或者设置save ""。

stop-writes-on-bgsave-error:在开启RDB且最近一次bgsave执行失败的情况下,如果该参数为yes,则Redis会阻止客户端的写入,直到bgsave执行成功。

rdbcompression:使用LZF算法压缩字符对象。

rdbchecksum:从RDB V5开始,在保存RDB文件时,会在文件末尾添加CRC64校验和,这样,能较容易的判断文件是否被损坏。但同时,对于带有校验和的RDB文件的保存和加载,会有10%的性能损耗。

dbfilename: RDB文件名。

dir:RDB文件保存的目录。

 

RDB的相关变量

复制代码
127.0.0.1:6379> info Persistence
# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1538447605
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:155648
复制代码

其含义如下:

loading: Flag indicating if the load of a dump file is on-going。是否在加载RDB文件

rdb_changes_since_last_save: Number of changes since the last dump。

rdb_bgsave_in_progress: Flag indicating a RDB save is on-going。是否在执行bgsave操作。

rdb_last_save_time: Epoch-based timestamp of last successful RDB save。最近一次bgsave操作时的时间戳。

rdb_last_bgsave_status: Status of the last RDB save operation。最近一次bgsave是否执行成功。

rdb_last_bgsave_time_sec: Duration of the last RDB save operation in seconds。最近一次bgsave操作花费的时间。

rdb_current_bgsave_time_sec: Duration of the on-going RDB save operation if any。当前bgsave操作已经执行的时间。

rdb_last_cow_size: The size in bytes of copy-on-write allocations during the last RBD save operation。COW的大小。指的是父进程与子进程相比执行了多少修改,包括读取缓冲区,写入缓冲区,数据修改等。

 

AOF

与RDB不一样的是,AOF记录的是命令,而不是数据。需要注意的是,其保存的是Redis Protocol,而不是直接的Redis命令。但是以文本格式保存。

 

如何开启AOF

只需将appendonly设置为yes就行。

 

AOF的工作流程:

1. 所有的写入命令追加到aof_buf缓冲区中。

2. AOF会根据对应的策略向磁盘做同步操作。刷盘策略由appendfsync参数决定。

3. 定期对AOF文件进行重写。重写策略由auto-aof-rewrite-percentage,auto-aof-rewrite-min-size两个参数决定。

 

appendfsync参数有如下取值:

no: don't fsync, just let the OS flush the data when it wants. Faster. 只调用系统write操作,不对AOF文件做fsync操作,同步硬盘操作由操作系统负责,通常同步周期最长为30s。

always: fsync after every write to the append only log. Slow, Safest. 命令写入到aof_buf后,会调用系统fsync操作同步到文件中。

everysec: fsync only one time every second. Compromise. 只调用系统write操作,fsync同步文件操作由专门进程每秒调用一次。

默认值为everysec,也是建议值。

 

重写机制

为什么要重写?重写后可以加快节点启动时的加载时间。

重写后的文件为什么可以变小?

1. 进程内超时的数据不用再写入到AOF文件中。

2. 存在删除命令。

3. 多条写命令可以合并为一个。

 

重写条件:

1. 手动触发

     直接调用bgrewriteaof命令。

2. 自动触发。

    与auto-aof-rewrite-percentage,auto-aof-rewrite-min-size两个参数有关。

    触发条件,aof_current_size > auto-aof-rewrite-min-size 并且 (aof_current_size  - aof_base_size) / aof_base_size >= auto-aof-rewrite-percentage。

    其中,aof_current_size是当前AOF文件大小,aof_base_size 是上一次重写后AOF文件的大小,这两部分的信息可从info Persistence处获取。

 

AOF重写的流程。

1. 执行AOF重写请求。

    如果当前进程正在执行bgsave操作,重写命令会等待bgsave执行完后再执行。

2. 父进程执行fork创建子进程。

3. fork操作完成后,主进程会继续响应其它命令。所有修改命令依然会写入到aof_buf中,并根据appendfsync策略持久化到AOF文件中。

4. 因fork操作运用的是写时复制技术,所以子进程只能共享fork操作时的内存数据,对于fork操作后,生成的数据,主进程会单独开辟一块aof_rewrite_buf保存。

5. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件中。每次批量写入磁盘的数据量由aof-rewrite-incremental-fsync参数控制,默认为32M,避免单次刷盘数据过多造成硬盘阻塞。

6. 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息。

7. 父进程将aof_rewrite_buf(AOF重写缓冲区)的数据写入到新的AOF文件中。

8. 使用新AOF文件替换老文件,完成AOF重写。

实际上,当Redis节点执行完一个命令后,它会同时将这个写命令发送到AOF缓冲区和AOF重写缓冲区。

 

Redis通过AOF文件还原数据库的流程。

1.  创建一个不带网络连接的伪客户端。因为Redis的命令只能在客户端上下文中执行。

2. 从AOF文件中分析并读取一条命令。

3. 使用伪客户端执行该命令。

4. 反复执行步骤2,3,直到AOF文件中的所有命令都被处理完。 

 

注意:AOF的持久化也可能会造成阻塞。

AOF常用的持久化策略是everysec,在这种策略下,fsync同步文件操作由专门线程每秒调用一次。当系统磁盘较忙时,会造成Redis主线程阻塞。

1. 主线程负责写入AOF缓冲区。

2. AOF线程负责每秒执行一次同步磁盘操作,并记录最近一次同步时间。

3. 主线程负责对比上次AOF同步时间。

   1> 如果距上次同步成功时间在2s内,主线程直接返回。

   2> 如果距上次同步成功时间超过2s,主线程会阻塞,直到同步操作完成。每出现一次阻塞,info Persistence中aof_delayed_fsync的值都会加1。

所以,使用everysec策略最多会丢失2s数据,而不是1s。

 

AOF的相关变量

复制代码
127.0.0.1:6379> info Persistence
# Persistence
...
aof_enabled:1
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:0
aof_current_size:19276803
aof_base_size:19276803
aof_pending_rewrite:0
aof_buffer_length:0
aof_rewrite_buffer_length:0
aof_pending_bio_fsync:0
aof_delayed_fsync:0
复制代码

其含义如下,

aof_enabled: Flag indicating AOF logging is activated. 是否开启AOF

aof_rewrite_in_progress: Flag indicating a AOF rewrite operation is on-going. 是否在进行AOF的重写操作。

aof_rewrite_scheduled: Flag indicating an AOF rewrite operation will be scheduled once the on-going RDB save is complete. 是否有AOF操作等待执行。

aof_last_rewrite_time_sec: Duration of the last AOF rewrite operation in seconds. 最近一次AOF重写操作消耗的时间。

aof_current_rewrite_time_sec: Duration of the on-going AOF rewrite operation if any. 当前正在执行的AOF操作已经消耗的时间。

aof_last_bgrewrite_status: Status of the last AOF rewrite operation. 最近一次AOF重写操作是否执行成功。

aof_last_write_status: Status of the last write operation to the AOF. 最近一次追加操作是否执行成功。

aof_last_cow_size: The size in bytes of copy-on-write allocations during the last AOF rewrite operation. 在执行AOF重写期间,分配给COW的大小。

 

如果开启了AOF,还会增加以下变量

aof_current_size: AOF current file size. AOF的当前大小。

aof_base_size: AOF file size on latest startup or rewrite. 最近一次重写后AOF的大小。

aof_pending_rewrite: Flag indicating an AOF rewrite operation will be scheduled once the on-going RDB save is complete.是否有AOF操作在等待执行。

aof_buffer_length: Size of the AOF buffer. AOF buffer的大小

aof_rewrite_buffer_length: Size of the AOF rewrite buffer. AOF重写buffer的大小。

aof_pending_bio_fsync: Number of fsync pending jobs in background I/O queue. 在等待执行的fsync操作的数量。

aof_delayed_fsync: Delayed fsync counter. Fsync操作延迟执行的次数。


如果一个load操作在进行,还会增加以下变量
loading_start_time: Epoch-based timestamp of the start of the load operation. Load操作开始的时间。

loading_total_bytes: Total file size. 文件的大小。

loading_loaded_bytes: Number of bytes already loaded.已经加载的文件的大小。

loading_loaded_perc: Same value expressed as a percentage. 已经加载的比例。

loading_eta_seconds: ETA in seconds for the load to be complete. 预计多久加载完毕。

 

AOF的相关参数

复制代码
appendonly yes
appendfilename "appendonly.aof"

appendfsync everysec

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

aof-load-truncated yes

aof-use-rdb-preamble no
复制代码

其中,

no-appendfsync-on-rewrite:在执行bgsave或bgrewriteaof操作时,不调用fsync()操作,此时,Redis的持久化策略相当于"appendfsync none"。

aof-load-truncated:在Redis节点启动的时候,如果发现AOF文件已经损坏了,其处理逻辑与该参数的设置有关,若为yes,则会忽略掉错误,尽可能加载较多的数据,若为no,则会直接报错退出。默认为yes。需要注意的是,该参数只适用于Redis启动阶段,如果在Redis运行过程中,发现AOF文件corrupted,Redis会直接报错退出。

aof-use-rdb-preamble:是否启用Redis 4.x提供的AOF+RDB的混合持久化方案,若为yes,在重写AOF文件时,Redis会将数据以RDB的格式作为AOF文件的开始部分。在重写之后,Redis会继续以AOF格式持久化写入操作。默认值为no。

 

参考:

1. 《Redis开发与运维》

2. 《Redis设计与实现》

3. 《Redis 4.X Cookbook》

与[转帖]深入理解Redis的持久化相似的内容:

[转帖]深入理解Redis的持久化

https://www.cnblogs.com/ivictor/p/9749465.html RDB RDB是将当前数据生成快照保存到硬盘上。 RDB的工作流程: 1. 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。

[转帖]深入理解Redis的scan命令

熟悉Redis的人都知道,它是单线程的。因此在使用一些时间复杂度为O(N)的命令时要非常谨慎。可能一不小心就会阻塞进程,导致Redis出现卡顿。 有时,我们需要针对符合条件的一部分命令进行操作,比如删除以test_开头的key。那么怎么获取到这些key呢?在Redis2.8版本之前,我们可以使用ke

[转帖]Redis线程模型的前世今生

https://www.jianshu.com/p/ea83267db47a 一、概述 众所周知,Redis是一个高性能的数据存储框架,在高并发的系统设计中,Redis也是一个比较关键的组件,是我们提升系统性能的一大利器。深入去理解Redis高性能的原理显得越发重要,当然Redis的高性能设计是一个

[转帖]Redis延迟问题怎么排查

https://www.yisu.com/zixun/574746.html 这篇文章主要讲解了“Redis延迟问题怎么排查”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Redis延迟问题怎么排查”吧! 使用复杂度高的命令 如果在使用Redis时,发

[转帖]Redis 运维实战 第01期:Redis 复制

https://cloud.tencent.com/developer/article/1986816 作者简介 马听,多年 DBA 实战经验,对 MySQL、 Redis、ClickHouse 等数据库有一定了解,专栏《一线数据库工程师带你深入理解 MySQL》作者。 从这篇文章开始,将出几期 R

[转帖]深入理解同步机制---内核自旋锁

https://switch-router.gitee.io/blog/spinlock/ 进程(线程)间的同步机制是面试时的常见问题,所以准备用一个系列来好好整理下用户态与内核态的各种同步机制。本文就以内核空间的一种基础同步机制—自旋锁开始好了 自旋锁是什么 自旋锁就是一个二状态的原子(atomi

[转帖]深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接

https://www.cnblogs.com/jiangjunli/p/10617034.html 1、内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符)。包括相等联接和自然联接。 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。例如,检索 students和course

[转帖]深入理解 netfilter 和 iptables

Netfilter (配合 iptables)使得用户空间应用程序可以注册内核网络栈在处理数据包时应用的处理规则,实现高效的网络转发和过滤。很多常见的主机防火墙程序以及 Kubernetes 的 Service 转发都是通过 iptables 来实现的。 关于 netfilter 的介绍文章大部分只

[转帖]深入理解以太网网线原理

https://zhuanlan.zhihu.com/p/568057983?utm_id=0 译者按:大部分人都知道,百兆以太网只用了 RJ45 端口中的 2 对 4 根线,分别为 TX、RX 的差分信号。 千兆以太网用了 RJ45 端口中的全部 4 对 8 根线,但是这 4 对 8 根线是怎么定

[转帖]深入理解虚拟机栈

一、背景 最近遇到个现象,hubble-api-open组件过段时间会内容占满,从而被K8S强制重启。 让我困惑的是,已经设置了-XX:MaxRAMPercentage=75.0,我觉得留有了一定的空间,不应该会占满,所以想深究下原因。 -XX:MaxRAMPercentage是设置JVM的最大堆内