Redis持久化

什么是 Redis 持久化?
Redis 持久化就是将 Redis 内存中的数据保存到磁盘文件中,避免因进程退出造成数据丢失问题,下次 Redis 重启时可以根据持久化文件恢复数据。

Redis支持RDB(Redis DataBase)和AOF(Append Only File)两种持久化方式。

1 RDB

在这里插入图片描述

1.1 触发机制

RDB持久化是把当前进程的数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发。

1.1.1 手动触发

手动触发有save个bgsave两个命令:

  • save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对内存较大的实例会造成长时间阻塞,线上环境不建议使用。
  • bgsave:Redis执行fork操作创建子线程,RDB持久化过程由子线程完成,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。

bgsave(background save)命令是针对save命令阻塞问题做的优化,因此Redis内部所有涉及RDB的操作都采用bgsave的方式,而save命令已经废弃。

1.1.2 自动触发

  1. 使用save相关配置,如"save m n"。表示m秒内,数据集存在n次修改时,自动触发bgsave。
  2. 如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给子节点。
  3. 执行debug reload命令重新加载Redis时,也会自动触发save操作。
  4. 默认情况下执行redis-cli shutdown命令时,如果没有开启AOF持久化功能,则自动执行bgsave。

1.2 流程说明

在这里插入图片描述

  1. 执行bgsave命令,Redis父进程判断当前是否有其它子进程正在执行,如RDB/AOF子线程,如果存在bgsave命令直接返回,如果不存在继续向下执行。
  2. 父进程执行fork命令创建子线程,fork操作过程中父进程会阻塞。
  3. 父进程fork完成后,bgsave命令会返回"Background saving started"信息,并不在阻塞父进程,父进程可以继续响应其它命令
  4. 子进程创建RDB文件,根据父进程内存数据生成临时快照文件,完成后对原有的RDB文件进行原子替换。
  5. 子进程向父进程发送信号,表明持久化过程已完成。

1.3 RDB文件的处理

1.3.1 保存

在 redis.conf 文件中配置rdb文件的路径和名字,配置完后需要重启Redis:

# 注意这里你必须指定一个目录,而不是文件名。
dir /data

# 用来存储数据库备份的文件名
dbfilename dump.rdb

以上配置表示存放在 /data目录下,dump.rdb文件中。

也可以通过以下命令动态配置,无需重启Redis:

config set dir{newDir}
config set dbfilename{newFileName}

执行完后,下一次Redis持久化rdb文件会保存到新的新目录。

运维提示
当遇到坏盘或磁盘写满的情况下,可以通过config set 命令在线修改文件路径到可用磁盘路径,之后执行bgsave进行磁盘切换,这个方法同样适用于AOF持久化文件。

1.3.2 压缩

Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认是开启的,通过在redis.conf中配置rdbcompression yes|no 开启或关闭:

# 在导出 .rdb 数据库时,是否使用 LZF 压缩字符串对象?
# 默认设置为 'yes',因为这几乎总是有益的。
# 如果你想在保存子进程中节省一些 CPU,可以设置为 'no',
# 但如果你有可压缩的值或键,数据集可能会更大。
rdbcompression yes

同样,可以使用config set命令在线开启/关闭压缩:

config set rdbcompression yes|no

1.3.3 校验

Redis提供的redis-check-dump工具检测RDB文件。

2 AOF

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令,达到恢复数据的目的。

AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式

2.1 使用AOF

开启AOF功能需要配置:appendonly yes,默认不开启。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同RDB持久化文件路径一致,都是通过dir指定。相关配置如下:

# 默认情况下,Redis会异步将数据集转储到磁盘上。这种模式在许多应用程序中已经足够好,但是如果Redis进程出现问题或者发生电力中断,
#可能会导致几分钟的写入丢失(具体取决于配置的保存点)。
#
# Append Only File是一种提供更好持久性的备选持久化模式。例如,使用默认的数据fsync策略(稍后在配置文件中可以看到),
#在像服务器断电这样的严重事件中,Redis可能只会丢失一秒钟的写入,
#或者如果Redis进程本身出现问题,但操作系统仍在正常运行,可能只会丢失一次写入。
#
# AOF和RDB持久化可以同时启用,没有问题。如果在启动时启用了AOF,Redis将加载AOF,这是具有更好持久性保证的文件。
#
# 请参阅http://redis.io/topics/persistence以获取更多信息。

appendonly yes

# append only文件的名称(默认:"appendonly.aof")
appendfilename "appendonly.aof"

# 工作目录。
#
# 数据库将写入此目录中,使用上面通过'dbfilename'配置指令指定的文件名。
#
# Append Only文件也将在此目录中创建。
#
# 请注意,你必须在这里指定一个目录,而不是文件名。
dir /data

在这里插入图片描述

  1. 命令写入(append):所有的写入命令会追加到aof_buf(缓冲区)中。
  2. 文件同步(sync):AOF缓冲区根据对应的策略向硬盘做同步操作。
  3. 文件重写(rewrite):随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
  4. 重启加载(load):当Redis服务器重启时,可以加载AOF文件进行重写。

2.2 命令写入

AOF命令写入的内容直接是文本协议格式。例如set hello world 这条命令,在AOF缓冲区会追加如下文本:

*2\n$6\nSELECT\n$1\n0\n*3\n$3\nset\n$5\nhello\n$5\nworld\n

在appendonly.aof文件中是如下内容:

*2
$6
SELECT
$1
0
*3
$3
set
$5
hello
$5
world

AOF为什么直接采用文本协议格式?

  • 文本协议具有很好的兼容性。
  • 开启AOF后,所有写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销。
  • 文本协议具有可读性,方便运维人员直接修改和处理。

AOF为什么把命令追加到aof_buf中?

  • Redis使用单线程响应命令,如果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。
  • 先写入缓冲区aof_buf中,Redis就可以向运维人员提供多种缓冲区同步到硬盘的策略,让运维人员做出选择,平衡性能和安全性。

2.3 文件同步

Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制,在redis.conf中配置:

# fsync()调用告诉操作系统实际将数据写入磁盘,而不是等待输出缓冲区中的更多数据。
# 一些操作系统会真正地将数据刷新到磁盘,而其他一些操作系统会尽快尝试这样做。
#
# Redis支持三种不同的模式:
#
# no:不进行fsync,只是让操作系统在需要时刷新数据。更快。
# always:每次向aof_buf写入后都进行fsync。较慢,但最安全。
# everysec:每秒只进行一次fsync。折衷方案。
#
# 默认值是"everysec",因为这通常是速度和数据安全性之间的正确折衷。由你来决定是否可以放宽到"no",
# 这将让操作系统在需要时刷新输出缓冲区,
# 以获得更好的性能(但如果你可以接受一些数据丢失,考虑默认的持久化模式,即快照),
# 或者反过来,使用"always",虽然非常慢,但比everysec稍微安全一些。
#
# 更多详细信息,请查看以下文章:
# http://antirez.com/post/redis-persistence-demystified.html
#
# 如果不确定,使用"everysec"。

# appendfsync always
appendfsync everysec
# appendfsync no

什么是write和fsync操作?

  • write操作会触发延迟写(delayed write)机制。Linux在内核提供页缓冲区用来提供硬盘IO性能。write操作再写入系统缓存区后直接返回。同步硬盘操作依赖于系统调度机制。例如:缓冲区页空间写满或达到特定时间周期。操作系统同步文件前,如果此时系统故障宕机,系统缓冲区内的数据将丢失。
  • fsync针对单个文件操作(如AOF文件),做强制硬盘同步,fsync将阻塞主进程,直到写入硬盘完成后返回,保证了数据的持久化。

always、everysec、no不同配置的区别?

在这里插入图片描述

参数说明详细描述操作
always每次向aof_buf写入之后都要进行fsync,较慢,但最安全命令写入aof_buf后调用fsync操作同步到AOF文件,fsync完成后线程返回fsync
everysec每秒进行一次fsync,折衷方案命令写入aof_buf后调用系统write操作,write完成后线程返回。fsync同步文件操作由专门线程每秒调用一次write + fsync
no不进行fsync,只是让操作系统在需要的时候刷新数据,更快命令写入aof_buf后调用系统的write操作,不对AOF文件做fsync同步,同步硬盘操作有操作系统负责,通常周期最长30swrite

在实际开发中,我们该如何配置appendfsync为何种参数?

  • 配置为always时,每次写入都要同步AOF文件,在一般的SATA硬盘上,Redis只能支持大约几百TPS(Transactions Per Second)写入,显然与Redis的高性能特性背道而驰,不建议配置。
  • 配置为no时,由于操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。
  • 配置为everysec时建议的同步策略(也是默认的同步策略),做到兼顾性能和数据安全性。理论上只有在系统突然宕机的情况下丢失1s的数据。

2.4 重写机制

随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制压缩文件体积

AOF文件重写就是把Redis进程内的数据转化为写命令同步到新的AOF文件的过程。

重写后的AOF文件为什么会变小?

  1. 进程内已超时的数据不再写入文件。
  2. 旧的AOF文件含有无效命令,如set a 1、set a 2、del key1、hdel key2、srem keys。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
  3. 多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。为了防止单挑命令过大造成客户端缓冲区溢出,对于list、set、hash、zset等类型操作,以64个元素为界拆分为多条。

为什么要进行AOF文件重写?

  1. 降低AOF文件占用的磁盘空间。
  2. 更小的AOF文件可以更快地被Redis加载。

如何触发AOF重写过程?

一、手动触发:直接调用bgrewriteaof命令。

在redis中执行以下命令:

set a 1
set a 2

打开appendonly.aof文件,发现记录了set a 1和set a 2操作,这两条操作在逻辑上只有set a 2会最终生效。
在这里插入图片描述

手动输入berewriteaof命令:
在这里插入图片描述
重写后只保留了set a 2
在这里插入图片描述
二、自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机
redis.conf文件中配置这两个参数:

# 自动重写AOF文件。
# 当AOF日志文件的大小增长到指定的百分比时,Redis能够自动地重写日志文件,隐式地调用BGREWRITEAOF。
#
# 这是它的工作方式:Redis会记住最后一次重写后的AOF文件的大小(如果自重启以来没有重写过,那么就使用启动时的AOF大小)。
#
# 这个基础大小会和当前大小进行对比。如果当前大小大于指定的百分比,那么就会触发重写。
# 同时,你需要指定一个AOF文件重写的最小大小,这对于避免因为达到了百分比增长但文件仍然相当小而重写AOF文件是有用的。
#
# 指定零百分比以禁用自动AOF重写功能。

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

auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 分别代表什么含义?

auto-aof-rewrite-min-size:表示自动进行AOF重写时,AOF文件需要满足的最小大小。

在这里插入图片描述
用aof_current_size表示当前AOF文件空间,用aof_base_size表示上一次重写后的AOF文件空间(如果没有重写过,就使用启动时的AOF文件大小),则公式可以改写为:

在这里插入图片描述
如果按照上面的redis.conf中的配置(64mb和100%)则

在这里插入图片描述

当触发AOF重写时,内部做了那些事情呢?

在这里插入图片描述

  1) 执行AOF重写请求,如果当前有其他子线程正在执行,如bgsabe/bgrewrite,则不执行。

  2) 父进程执行fork命令创建子进程,开销等同于bgsave命令。

  3) 由于fork操作采用写时复制技术,子进程只能共享父进程fork操作时的内存数据。fork操作后,父进程会继续响应命令,但是此时对内存的修改是放在一个新的内存区域的,子进程不能共享。为了不丢失这部分数据,Redis使用“AOF重写缓冲区”来保存这部分新数据,防止新AOF文件生成期间丢失这部分更新数据。

  4) 子进程根据内存快照,按照命令合并规则写入到新的AOF文件中。

  5.1) 新AOF文件写入完成后,子进程发送信号给父进程。

  5.2) 父进程把AOF重写缓冲区的数据写入到新的AOF文件中。

  5.3) 使用新的AOF文件替换老文件,完成AOF重写。

  6) 完成文件重写后,主进程继续响应其他命令。所有修改命令存入AOF缓冲区,并按照appendfsync策略同步到硬盘。

什么是写时复制技术?

写时复制(Copy-on-Write,简称COW)
在fork操作中,子进程被创建为父进程的一个副本。这意味着子进程在初始状态下会共享父进程的所有内存数据。然而,为了提高效率并节省内存,系统并不会立即将所有数据复制到子进程,而是采用写时复制技术。

写时复制技术的工作方式是,当父进程或子进程试图修改某部分共享内存时,系统才会创建该内存区域的一个副本,供修改的进程使用。这样,只有在必要时才会进行实际的数据复制,从而节省了大量的内存资源,并提高了程序的运行效率。

所以,这句话的意思是,子进程只能共享父进程在fork操作时的内存数据,任何后续的修改都不会被共享,因为这些修改会在写时复制的过程中被复制到新的内存区域。

2.5 重启加载

AOF和RDB文件都可以用于服务器重启时的数据恢复。

在这里插入图片描述

  1. AOF持久化开启(appendonly yes)且存在AOF文件时,优先加载AOF文件。
  2. AOF关闭时加载RDB文件。
  3. 加载AOF/RDB文件成功后,Redis启动成功。
  4. AOF/RDB文件存在错误是,Redis启动失败。

2.6 文件校验

加载损坏的AOF文件时会拒绝启动。

AOF文件可能存在结尾不完整的情况,例如机器突然断电导致AOF尾部文件命令写入不全。可以使用aof-load-truncated配置来兼容这种情况:

# 在Redis启动过程中,当AOF数据被重新加载到内存中时,可能会发现AOF文件在末尾被截断。
# 这可能会发生在Redis运行的系统崩溃时,尤其是在没有data=ordered选项的情况下挂载了ext4文件系统的情况下(然而,当Redis本身崩溃或中止,
# 但操作系统仍然正常工作时,这是不会发生的)。
#
# 当这种情况发生时,Redis可以选择退出并报错,或尽可能多地加载数据(现在的默认行为)并启动,
# 如果发现AOF文件在末尾被截断。以下选项用于控制这种行为。
#
# 如果aof-load-truncated设置为yes,那么被截断的AOF文件将被加载,Redis服务器开始发出日志,通知用户此事件。
# 否则,如果选项设置为no,服务器将中止并报错,拒绝启动。当选项设置为no时,
# 用户需要使用"redis-check-aof"工具修复AOF文件,然后再重新启动服务器。
#
# 请注意,如果发现AOF文件在中间部分被损坏,服务器仍然会退出并报错。
# 这个选项只在Redis试图从AOF文件中读取更多数据,但找不到足够的字节时才适用。
aof-load-truncated yes

3 问题定位与优化

Redis持久化功能一直是影响Redis性能的高发地。

3.1 fork操作

当Redis做RDB或AOF重写时,一个必不可少的操作就是执行fork操作创建子线程,对于大多数操作系统来说,fork是个重量级操作。

虽然fork操作不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。

对于10GB的Redis进程,需要复制大约20M的内存页表。

因此,fork操作耗时更进程总内存息息相关。

对于高流量的Redis实例,OPS(Operations Per Second)可达5万以上,如果fork耗时操作在秒级,将拖累几万条Redis命令的执行,对线上应用延迟影响非常明显。

如何改善fork操作耗时?

  1. 优先使用物理机或高效支持fork操作的虚拟化技术。
  2. 控制Redis实例最大可用内存,因为fork的耗时量跟Redis内存成正比。
  3. 合理配置Linux内存分配策略,避免物理内存不足导致fork失败。
  4. 降低fork操作频率,如放宽AOF自动触发时机。

3.2 子进程开销监控和优化

子进程负责AOF或RDB文件的重写,它的运行主要涉及CPU、内存、硬盘。

什么是no-appendfsync-on-rewrite参数?

# 当AOF fsync策略被设置为始终或每秒一次,并且后台保存进程(后台保存或AOF日志后台重写)正在对磁盘进行大量的I/O操作,
# 在某些Linux配置中,Redis可能会在fsync()调用上阻塞太长时间。请注意,目前没有解决办法,
# 因为即使在不同的线程中执行fsync也会阻塞我们的同步写入(2)调用。
#
# 为了缓解这个问题,可以使用以下选项,该选项将阻止在BGSAVE或BGREWRITEAOF进行中的主进程中调用fsync()。
#
# 这意味着,当另一个子进程正在保存时,Redis的持久性与"appendfsync none"相同。实际上,这意味着在最坏的情况下(使用默认的Linux设置),
# 可能会丢失多达30秒的日志。
#
# 如果你有延迟问题,将此设置为"yes"。否则,将其保留为"no",这是从持久性角度来看最安全的选择。

no-appendfsync-on-rewrite no

3.3 AOF追加阻塞

什么是AOF追加阻塞?

当开启AOF持久化时,常用的同步硬盘的策略时ererysec,用于平衡数据性能和安全性。对于这种方式,Redis使用另一条线程每秒执行fsync同步硬盘。当系统盘繁忙的时候,会造成Redis主线程阻塞。

为什么everysec配置最多可能丢失2秒数据, 不是1秒?

因为主线程负责对比上次AOF同步时间:

  • 如果距上次同步时间在2s内,主线程之间返回。
  • 如果距上次同步时间超过2s,主线程将会阻塞,直到同步操作完成。

所以,对于小于2s内的命令,主线程都会通过,导致aof_buf里面存储了2s的数据,所以如果发生数据丢失,最多会丢失2s。

在这里插入图片描述

4 多实例部署

为什么要在同一个服务器上多实例部署Redis?

因为线程的服务器CPU通常为多核CPU,而Redis是一个单线程架构,如果多核服务器上只部署一个Redis实例,会导致无法充分利用CPU的资源,所以要部署多个Redis实例。

同一个服务器上部署多个Redis实例会有什么问题?

如果多个Redis实例都开启了AOF文件重写,彼此之间会对CPU和IO有竞争。所以需要有一种措施,把子进程的工作隔离。

这个措施是什么呢?

由于Redis在info Persistenc中为我们提供了监控子进程运行状况的度量指标,我们可以依据这些指标,通过外部程序轮询控制AOF重写操作的执行。

在这里插入图片描述
执行流程如下:

  1. 外部程序定时轮询服务器上所有Redis实例。
  2. 对于开启AOF的实例,查看(aof_current_size / aof_base_size)确认增长率。
  3. 如果增长率超过特定的阈值(如100%),外部程序执行bgrewriteaof命令触发当前实例的AOF重写。
  4. 运行期间循环检查aof_rewrite_in_progress和aof_current_rewrite_time_sec指标,直到AOF重写结束。
  5. 确认实例AOF重写完成后,再检查其它实例并重复2-4步。

以上执行流程保证机器内每个Redis实例AOF重写能够串行化执行。

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值