4. Redis的配置文件以及持久化

配置文件

Redis 最大的一个特点就是它的配置文件行数非常多,加上注释大概一千三四百行,里面有大量的配置可以供我们进行设置。其实关于 Redis 的配置我们之前也提到过,比如:开启多线程、设置线程数、数据结构内部存储元素的数量限制等等,那么下面我们就来介绍一下 Redis 配置文件中一些其它的常见配置项。

不过提到了配置文件,这里再提一个命令,叫做 config,是专门获取当前 Redis 配置项的。

127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "satori"
127.0.0.1:6379> config get dir
1) "dir"
2) "/root"
127.0.0.1:6379>

config get requirepass查看密码,如果没有设置的话会返回空字符串。config get dir 查看 redis-server 的启动路径,我们是在 /root 下启动的。既然有get,那就有set。

127.0.0.1:6379> config set requirepass 123456
OK  # 设置密码,一旦设置立即生效
127.0.0.1:6379> auth 12345  # 验证
(error) WRONGPASS invalid username-password pair
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> config set requirepass satori
OK  # 这里我们再改回来

下面我们来介绍配置文件,首先根据功能的不同,我们可以将 Redis 的配置文件分为以下几个部分,我们分别来介绍,当然 Redis 内部就是这么分的。

 

units

# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

里面定义了一些基本单位,而且大小写不敏感。

 

includes

# include /path/to/local.conf
# include /path/to/other.conf

可以将配置写在其他地方,然后 include 进来,就跟编程语言里面的模块一样,如果都写在一个文件里面,会比较多,因此可以通过导入的方式。

 

network

# 默认本地连接,如果想支持其他机器连接的话,那么把 127.0.0.1 改成 0.0.0.0
bind 127.0.0.1

# 保护模式,如果改成 no,那么任意机器都可以连接,并且不需要认证
# 因此我们都会设置成 yes,开启保护模式,通过 bind 和 requirepass 来实现认证登录
protected-mode yes

# 监听的端口
port 6379

# 设置 tcp 的 backlog,backlog 其实是一个连接队列,backlog 队列总和 = 未完成三次握手队列 + 已完成三次握手队列
# 在高并发环境下你需要一个高 backlog 值来避免慢客户端连接问题
# 注意 linux 内核会将这个值减小到 /proc/sys/net/core/somaxconn 的值
# 所以需要增大 somaxconn 和 tcp_max_syn_backlog 两个值来达到想要的结果
tcp-backlog 511

# 当客户端 N 秒没有活动,那么关闭连接,0 表示禁用该功能,一直保持连接
timeout 0

# 如果是 redis 集群,那么每隔 300 秒发送一个信息,告诉主节点自己还活着。
tcp-keepalive 300

因此可以看到 network 是和网络连接相关的。

 

general

# 是否是以守护进程方式启动,默认是 no,但是一般我们会改成 yes,也就是让 redis 后台启动
daemonize no

# redis 的 pid 管道文件
pidfile /var/run/redis_6379.pid

# 日志级别:debug、verbose、notice、warning,级别越高,打印的信息越少。
# 刚开发会选择 debug,会打印详细日志,上线之后选择 notice 或者 warning
loglevel notice

# 日志名字
logfile ""

# 是否把日志输出到系统日志里面,默认是不输出
# syslog-enabled no

# 指定日志文件标识,这里是 redis,所以就是 redis.log
# syslog-ident redis

# 指定 syslog 设备,值可以是 user 或者 local0 到 local7
# syslog-facility local0

# redis 数据库的数量,默认是 16 个,0-15
databases 16

# 总是显示 logo
always-show-logo yes

这部分对应的是一些通用的配置。

 

SECURITY

# 设置密码,一旦设置,再使用 redis-cli 连接的时候就需要指定密码了
# 否则进去之后无法执行命令,可以使用 redis-cli -a password,但是这样密码就暴露在终端中
# 尽管能连接,但是 redis 提示你不安全。当然我们还可以进去之后通过 auth password 来设置
# requirepass foobared

 

CLIENT

# 设置客户端的最大连接数量,默认是 10000
# maxclients 10000

 

MEMORY MANAGEMENT

# 最大内存
# maxmemory <bytes>

# 当你的内存达到极限的时候,肯定要清除缓存,那么你要选择哪种策略
# volatile-lru:使用 LRU(最近最少使用) 策略移除 keys,只针对过期的 keys
# allkeys-lru:使用 LRU(最近最少使用) 策略移除 keys
# volatile-lfu:使用 LFU(最近最不常使用) 策略移除 keys,只针对过期的 keys
# allkeys-lru:使用 LFU(最近最不常使用) 策略移除 keys
# volatile-random:随机移除一个过期的 key
# allkeys-random:随机移除一个任意 key
# volatile-ttl:移除 ttl值(过期时间) 最少的 key,即最快要过期的 key
# noeviction:不移除任意 key,仅仅在写操作的时候返回一个 error

# 默认是noeviction
# maxmemory-policy noeviction

# LRU 和 LFU 都并非精确的算法,而是估算值,因此你可以设置样本的大小,默认是 5 个
# maxmemory-samples 5

# 从节点是否忽略 maxmemory 配置,针对 redis 集群,并且是从 redis 5 开始才有这个配置
# replica-ignore-maxmemory yes

以上就是一些我们经常使用的配置,当然还有一部分与持久化有关的配置,我们会在下面介绍持久化的时候说。

Redis的持久化

Redis 的一大特点就是可以将数据持久化,说白了就是将内存中的数据写入到磁盘保证不丢失,并且还能将数据从磁盘加载到内存。我们之前的操作都是基于内存,因此性能很高,然而一旦关闭程序,那么数据就丢失了。因此我们需要在指定的时间间隔内将内存的数据写入到磁盘,也就是行话讲的 Snapshot 快照,它恢复时是将快照文件直接写入到内存里面。

这也是 Redis 和 Memcached 的主要区别之一,因为 Memcached 不具备持久化功能。

而数据持久化,Redis 为我们提供了以下几种方式:

  • 快照方式(RDB,Redis DataBase):将某一个时刻的内存数据,以二进制的方式写入磁盘;
  • 文件追加方式(AOF,Append Only File),记录所有的操作命令,并以文本的形式追加到文件中;
  • 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能减低数据丢失的风险。

因为每种持久化方案,都有特定的使用场景,我们分别介绍。

RDB

RDB(Redis DataBase)是将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘的过程。内存快照就是我们上面说的,它指内存中的数据在某一个时刻的状态记录,类似于拍照片,当你给朋友拍照时,一张照片就能把朋友一瞬间的形象全部记录下来。

而触发 RDB 的方式也分为两种:一类是手动触发,另一类是自动触发。

1)手动触发

手动触发持久化的操作有两个: savebgsave ,它们主要区别体现在:是否阻塞 Redis 主线程的执行。

1. save 命令

在客户端中执行 save 命令,就会触发 Redis 的持久化,但同时也会使 Redis 处于阻塞状态,直到 RDB 持久化完成,才会响应其他客户端发来的命令,所以在生产环境一定要慎用。

127.0.0.1:6379> save
OK
127.0.0.1:6379>

save 执行命令的流程如图所示:

2. bgsave 命令

bgsave(background save)为后台保存, 它和 save 命令最大的区别就是 bgsave 会 fork 一个子进程来执行持久化,整个过程中只有在 fork 子进程时有短暂的阻塞,当子进程被创建之后,Redis 的主进程就可以响应其他客户端的请求了,相对于整个流程都阻塞的 save 命令来说,显然 bgsave 命令更适合我们使用。

127.0.0.1:6379> bgsave
Background saving started  # 提示开始后台保存
127.0.0.1:6379> 

bgsave 执行命令的流程如图所示:

2)自动触发

说完手动触发之后,再来看看自动触发,关于自动触发的条件我们是可以在配置文件中进行配置的。

1. save m n

save m n 是指在 m 秒内,如果有 n 个键发生改变,则自动触发持久化。 参数 m 和 n 可以在 Redis 的配置文件中找到,例如,save 60 1 则表明在 60 秒内,只要有一个键发生改变,就会触发 RDB 持久化。 自动触发持久化,本质是 Redis 通过判断,如果满足设置的触发条件,自动执行一次 bgsave 命令。 注意:当设置多个 save m n 命令时,满足任意一个条件都会触发持久化。 例如,我们设置了以下两个 save m n 命令:

save 60 10
save 600 20

当 60s 内如果有 10 次 Redis 键值发生改变,就会触发持久化;如果 60s 内 Redis 的键值改变次数少于 10 次,那么 Redis 就会判断 600s 内,Redis 的键值是否至少被修改了 20 次,如果满足则会触发持久化。

2. flushall

flushall 命令用于清空 Redis 数据库,在生产环境下一定慎用,当 Redis 执行了 flushall 命令之后,则会触发自动持久化,把 RDB 文件清空。

3. 主从同步触发

在 Redis 主从复制中,当从节点执行全量复制操作时,主节点会执行 bgsave 命令,并将 RDB 文件发送给从节点,该过程会自动触发 Redis 持久化。

 

RDB 配置说明

合理的设置 RDB 的配置,可以保障 Redis 高效且稳定的运行,下面一起来看 RDB 的配置项都有哪些?

################################ SNAPSHOTTING  ################################
# 如果满足以下策略,就会将数据同步到磁盘
# 900 秒内有 1 次修改、300 秒内有 10 次修改、60 秒内有 10000 次修改
# 任何一个条件满足,都会将文件写入到磁盘上,当然这数值我们是可以修改的
# 如果想立刻备份,那么直接在命令行输入 save 即可,会立刻备份,此时会处于阻塞状态,其他所有命令都会阻塞
# 也可以输入 bgsave 命令进行备份,和 save 不同的是,bgsave 是在后台异步进行快照。
# 并且还可以通过 lastsave 命令获取最后一次成功执行快照的时间
save 900 1
save 300 10
save 60 10000

# 当快照操作出错时停止写数据到磁盘,这样后面写操作均会失败。
# 比如内存 4GB,但是当前主进程使用的 3GB
# 那么将数据写入磁盘、fork 一份主进程的时候就又需要额外的 3GB,显然内存不够了,因此保存到硬盘的数据也就失败了
# 为了不影响后续写操作,可以将该项值改为 no
stop-writes-on-bgsave-error yes

# 是否压缩,默认是 yes,采用 lzf 方式压缩。
# 如果不想消耗 CPU 性能来进行文件压缩的话,可以设置为 no,缺点是需要更多的空间来存储文件
rdbcompression yes

# 对 rdb 数据进行校验, 表示表示写入文件和读取文件时是否开启 RDB 文件检查
# 检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。
# 这个过程耗费 CPU 资源,会大概增加 10% 的性能损耗,默认为 yes
rdbchecksum yes

# rdb 文件名,所以默认是 rdb,并且也是默认开启 rdb 持久化
# 如果把上面所有的 save 给注释掉,那么就相当于关闭 rdb
dbfilename dump.rdb

# 路径,这里的路径,如果你不单独指定,那么默认是 redis 的启动路径
# 也就是你是在哪个目录下启动的 redis-server,那么 dir 就是哪里,但同时这也是 rdb 文件的所在路径
# 比如我是在 /root 下面执行的 redis-server,那么 dir 就是 /root,同时 rdb 文件的路径也是 /root/dump.rdb
dir ./

Redis 中可以使用命令查询当前配置参数。查询命令的格式为:config get xxx ,例如,想要获取 RDB 文件的存储名称设置,可以使用 config get dbfilename ,执行效果如下:

127.0.0.1:6379> config get dbfilename
1) "dbfilename"
2) "dump.rdb"

设置 RDB 的配置,可以通过以下两种方式:

  • 1. 手动修改 Redis 配置文件
  • 2. 使用命令行设置,config set dir "/usr/data"就是修改RDB文件的存储命令

注意:redis.conf 里面配置都可以通过 config get xxx 进行获取、config set xxx value 进行修改,手动修改 Redis 配置文件的方式是全局生效的,即重启 Redis 服务器设置参数也不会丢失,而使用命令修改的方式,在 Redis 重启之后就会丢失。但手动修改 Redis 配置文件想要立即生效需要重启 Redis 服务器,而命令的方式则不需要重启 Redis 服务器。

RDB 文件恢复

当 Redis 服务器启动时,如果 Redis 根目录存在 RDB 文件 dump.rdb,Redis 就会自动加载 RDB 文件恢复持久化数据。 如果根目录没有 dump.rdb 文件,请先将 dump.rdb 文件移动到 Redis 的根目录。当然 Redis 在启动时有日志信息,会显示是否加载了 RDB 文件。

Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

到现在我们已经知道了 RDB 持久化分为手动触发和自动触发两种方式,它的优点是存储文件小,Redis 启动时恢复数据比较快,缺点是有丢失数据的风险。RDB 文件的恢复也很简单,只需要把 RDB 文件放到 Redis 的根目录,在 Redis 启动时就会自动加载并恢复数据。

RDB优缺点

1)RDB 优点

  • RDB 的内容为二进制的数据,占用内存更小,更紧凑,更适合做为备份文件;
  • RDB 对灾难恢复非常有用,它是一个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务恢复;
  • RDB 可以更大程度的提高 Redis 的运行速度,因为每次持久化时 Redis 主进程都会 fork 一个子进程,进行数据持久化到磁盘,Redis 主进程并不会执行磁盘 I/O 等操作;
  • 与后面介绍的 AOF 格式的文件相比,RDB 文件可以更快的重启。

2)RDB 缺点

  • 因为 RDB 只能保存某个时间间隔的数据,如果中途 Redis 服务被意外终止了,则会丢失一段时间内的 Redis 数据;
  • RDB 需要经常 fork 才能使用子进程将其持久化在磁盘上。如果数据集很大,fork 可能很耗时,并且如果数据集很大且 CPU 性能不佳,则可能导致在几毫秒甚至一秒钟内 Redis 无法为客户端服务。

当然我们也可以禁用持久化,从而提高 Redis 的执行效率,如果对数据丢失不敏感的情况下,可以在连接客户端的情况下,执行 config set save "" 命令即可禁用 Redis 的持久化。而在 redis.conf 中,如果把 save 开头的那几个配置全给注释掉,那么也会禁止持久化。但一般不会这么做。

快照时数据能修改吗?

在给别人拍照时,一旦对方动了,那么这张照片就拍糊了,我们就需要重拍,所以我们当然希望对方保持不动。对于内存快照而言,我们也不希望数据 "乱动"。

举个例子。我们在时刻 t 给内存做快照,假设内存数据量是 4GB,磁盘的写入带宽是 0.2GB/s,简单来说至少需要 20s(4/0.2 = 20)才能做完。如果在时刻 t+5s 时,一个还没有被写入磁盘的内存数据 "A",被修改成了 "A1",那么就会破坏快照的完整性,因为 A1 不是时刻 t 时的状态。因此和拍照类似,我们在做快照时也不希望数据 "乱动",也就是不能被修改。

但如果快照执行期间数据不能被修改,是会有潜在问题的。对于刚刚的例子来说,在做快照的 20s 时间里,如果这 4GB 的数据都不能被修改,Redis 就不能处理对这些数据的写操作,那无疑就会给业务服务造成巨大的影响。

你可能会想到,可以用 bgsave 避免阻塞啊,这里就要说到一个常见的误区了:避免阻塞和正常处理写操作并不是一回事。虽然主线程的确没有阻塞,可以正常接收请求,但是为了保证快照的完整性,它只能处理读操作,不能像正常情况那样处理写操作(修改正在执行快照的数据)。

为了快照而暂停写操作,肯定是不能接受的。所以这个时候,Redis 就会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。

此时,如果主线程对这些数据也都是读操作,那么主线程和 bgsave 子进程相互不影响。但如果主线程要修改一块数据,那么这块数据就会被复制一份,生成该数据的副本。然后 bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。

这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

快照应该多久写一次?

根据 Redis 配置文件我们得知,默认情况下如果 900 秒内有 1 次修改、300 秒内有 10 次修改、60 秒内有 10000 次修改,那么会进行快照生成。

save 900 1
save 300 10
save 60 10000

但是问题来了,Redis 的配置是通用的,不一定适合我们当前的业务,那么我们应该多久做一次快照呢?

首先我们说 RDB 会有数据丢失的可能,因为它是每隔一段时间判断一次,如果条件满足,那么触发一次写快照。但问题是如果在数据修改之后、但还没有触发写快照时机器宕机了,那么数据就会丢失。举个栗子:

首先如果想数据尽可能少的丢失,那么快照的时间间隔就设置的短一些,就类似于相机的连拍。这样一来,快照的间隔时间变得很短,丢失的数据的可能性也会大大降低。那么问题来了,我们可不可以每秒做一次快照呢?毕竟,每次快照都是由 bgsave 子进程在后台执行,也不会阻塞主线程。

这种想法其实是错误的。虽然 bgsave 执行时不阻塞主线程,但如果频繁地执行全量快照,也会带来两方面对的开销。

  • 1. 频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
  • 2. 另一方面,bgsave 子进程需要通过 fork 操作从主进程创建出来,虽然子进程在创建后不会再阻塞主进程,但是 fork 这个创建过程本身是会阻塞的,而且内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主进程了。

那么,有什么其他好方法吗?显然我们可以做增量快照,所谓增量快照就是指做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。

假设第一次做完了全量快照,之后如果再做快照,我们只需要将被修改的数据写入快照文件就行。但是这么做的前提是,Redis 需要记录哪些数据被修改了,而这个 "记住" 功能又需要 Redis 使用额外的元数据信息去记录哪些数据被修改了,这又会带来额外的空间开销问题。如果我们对每一个键值对的修改,都做个记录,那么假设有 1 万个被修改的键值对,我们就需要有 1 万条额外的记录。而且有的时候键值对非常小,比如只有 32 字节,而记录它被修改的元数据信息,可能就需要 8 字节,此时为了 "记住" 修改而引入的额外空间开销就比较大。这对于内存资源宝贵的 Redis 来说,有些得不偿失。

到这里我们可以发现,虽然快照的恢复速度快,但快照的频率不好把握,如果频率太低,两次快照间一旦宕机,就可能有比较多的数据丢失。如果频率太高,又会产生额外开销,那么有什么方法解决呢?那么接下来就引入 Redis 的另一种持久化方式 AOF,将 RDB 和 AOF 结合起来才是最佳的解决方案。

AOF

Redis 的持久化除了 RDB 还有 AOF,我们知道 RDB 是保存某个时间间隔的数据,而且要达到某个触发条件才会进行持久化。但是这样有一个风险,那就是在持久化之前机器突然宕机了,那么这个时间间隔内的最新数据就会丢失。比如 600s 秒内变化 10 个 key 才会持久化,但是当变化 9 个 key 的时候宕机了,那么这 9 个 key 的数据就丢失了。

可能会操作 Redis 服务意外终止的条件:

  • 安装 Redis 的机器停止运行,蓝屏或者系统崩溃;
  • 安装 Redis 的机器出现电源故障,例如突然断电;
  • 使用 kill -9 Redis_PID 等。

那么如何解决以上的这些问题呢?Redis 为我们提供了另一种持久化的方案——AOF。

AOF(Append Only File)指的是追加到文件,所以 AOF 我们可以猜测它应该类似于日志文件一样,事实上也确实如此。但它和我们熟悉的数据库的预写式日志(Write Ahead Log,WAL)不同,WAL 是先把修改的数据记到日志文件中,然后执行相关操作;而 AOF 正好相反,Redis 是先执行命令、将数据写入内存,然后才记录日志。

那为什么要先执行命令再记日志呢?要回答这个问题,我们要先知道 AOF 日志文件里面记录了什么内容。传统数据库的日志,例如 redo log(重做日志),记录的是修改后的数据,而 AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。我们以 "set name hanser" 这条命令为例,看看 Redis 收到之后会在 AOF 文件中记录什么东西吧?

其中 *3 表示当前命令中有 3 个部分,每个部分上面都会有一个 $数字,表示该部分占了多少字节,比如 $4 表示 name 这个 key 占了 4 个字节。但是为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查,因此要是先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。而 AOF 这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到 AOF 日志文件中,否则系统就会直接向客户端报错,所以 Redis 使用 AOF 这一方式的一大好处就是可以避免出现记录错误命令的情况。

除此之外,AOF 还有一个好处:它是在命令执行后才记录日志,所以不会阻塞当前的写操作。

持久化查询和设置

1)查询 AOF 启动状态

使用 config get appendonly 命令,如下图所示:

127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "no"
127.0.0.1:6379> 

第一行为 AOF 日志文件的名称,第二行表示 AOF 的启动状态:yes 表示启动、no 表示为启动。

2)开启 AOF 持久化

Redis 默认是关闭 AOF 持久化的,想要开启 AOF 持久化,有以下两种方式:

  • 通过命令行的方式;
  • 通过修改配置文件的方式(redis.conf)

分别看两种方式的实现:

1. 命令行启动 AOF,使用 config set appendonly yes 命令,如下所示:

127.0.0.1:6379> config set appendonly yes
OK
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "yes"
127.0.0.1:6379> 

命令行启动 AOF 的优缺点:命令行启动的优点是无需重启 Redis 服务,缺点是如果 Redis 服务重启,则之前使用命令行设置的配置就会失效。

2. 配置文件启动 AOF

只需要在配置文件 redis.conf 中设置 appendonly yes 即可,默认 appendonly no 表示关闭 AOF 持久化。 配置文件启动 AOF 的优缺点:修改配置文件的缺点是每次修改配置文件都要重启 Redis 服务才能生效,优点是无论重启多少次 Redis 服务,配置文件中设置的配置信息都不会失效。

触发持久化

1)自动触发

有两种情况可以自动触发 AOF 持久化,分为是:"满足 AOF 设置的持久化策略触发" 和 "满足 AOF 重写触发"。其中,AOF 重写触发会在本文的后半部分详细介绍,这里重点来说 AOF 持久化策略都有哪些。 AOF 持久化策略,分为以下三种:

  • always:每条 Redis 操作命令都会写入磁盘,最多丢失一条数据;
  • everysec:每秒钟写入一次磁盘,最多丢失一秒的数据;
  • no:不设置写入磁盘的规则,根据当前操作系统来决定何时写入磁盘,Linux 默认 30s 写入一次数据至磁盘。

这三种配置可以在 Redis 的配置文件(redis.conf)中设置,如下代码所示:

# 开启每秒写入一次的持久化策略
appendfsync everysec
# 或者通过命令行进行设置:config set appendfsync everysec

因为每次写入磁盘都会对 Redis 的性能造成一定的影响,所以要根据用户的实际情况设置相应的策略,一般设置每秒写入一次磁盘的频率就可以满足大部分的使用场景了。

2)手动触发

在客户端执行 bgrewriteaof 命令就可以手动触发 AOF 持久化。

和 RDB 持久化类似,RDB 持久化会生成 dump.rdb 文件,AOF 持久化会生成 appendonly.aof 文件。但是注意:在往 AOF 文件中记录日志的时候,是由主线程来完成的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。

AOF 文件重写

AOF 是通过记录 Redis 的执行命令来持久化(保存)数据的,所以 AOF 文件会随着时间变得越来越大,这样不仅增加了服务器的存储压力,也会造成 Redis 重启速度变慢,为了解决这个问题 Redis 提供了 AOF 重写的功能。

1)什么是AOF重写?

AOF 重写指的是它会直接读取 Redis 服务器当前的状态,并压缩保存为 AOF 文件。例如,我们增加了一个计数器,并对它做了 99 次修改,如果不做 AOF 重写的话,那么持久化文件中就会有 100 条记录执行命令的信息,而 AOF 重写之后,之后记录一条此计数器最终的结果信息,这样就去除了所有的无效信息。

2)AOF重写实现

触发 AOF 文件重写,要满足两个条件,这两个条件也是配置在 Redis 配置文件中的,它们分别是:

  • auto-aof-rewrite-min-size:允许 AOF 重写的最小文件容量,默认是 64mb。
  • auto-aof-rewrite-percentage:AOF 文件重写的大小比例,默认值是 100,表示 100%,也就是只有当前 AOF 文件,比最后一次(上次)的 AOF 文件大一倍时,才会启动 AOF 文件重写。

查询 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 的值,可使用 config get xxx 命令,如下所示:

127.0.0.1:6379> config get auto-aof-rewrite-min-size
1) "auto-aof-rewrite-min-size"
2) "67108864"
127.0.0.1:6379> config get auto-aof-rewrite-percentage
1) "auto-aof-rewrite-percentage"
2) "100"
127.0.0.1:6379> 

只有同时满足 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 设置的条件,才会触发 AOF 文件重写。

3)AOF重写流程

首先 Redis 写入 AOF 是由主线程完成的,但重写则不一样,重写时会 fork 一个 bgrewriteaof 子进程,这也是为了避免阻塞主线程,导致 Redis 性能下降。AOF 文件重写是生成一个全新的文件,并把当前数据的最少操作命令保存到新文件上,当把所有的数据都保存至新文件之后,Redis 再交换两个文件即可。由于主线程不阻塞,所以在重写的时候依旧可以处理命令,并且这些最新的操作也会被记录到新的 AOF 文件中。

配置说明

合理的设置 AOF 的配置,可以保障 Redis 高效且稳定的运行,以下是 AOF 的全部配置信息和说明。

AOF 的配置参数在 Redis 的配置文件中,配置参数和说明如下:

############################## APPEND ONLY MODE ###############################
# aof 意思是 append only file,我们看到默认是 no,表示关闭,因此持久化默认使用 rdb
appendonly no


# 正如 dump.rdb,aof 方式持久化也就一个文件名,默认叫appendonly.aof
appendfilename "appendonly.aof"
# 另外 aof 和 rdb 持久化方式是可以同时指定的,后面会说
# 另外我们知道当 redis 启动的时候,会加载文件,读进缓存。
# 但如果既有 rdb 文件又有 aof 文件,那 redis 会读取哪个呢?实际上会读取 aof 文件
# 如果 aof 被人乱搞了一通,那么会发现 redis 无法启动,启动了也连接不上。
# 这个时候有两种办法,一种是删除相应的 aof 文件,但是这样数据就丢了
# 还有一种方法,就是 redis-server 所在路径 (一般是/usr/local/bin) 中,有一个 redis-check-aof
# 通过 redis-check-aof --fix appendonly.aof 可以进行修复,同理还有一个 redis-check-rdb,用来修复 rdb 文件。


# appendfsync:同步策略,支持三个参数
# 1.always:同步持久化,每次发生数据变更会被立即记录到磁盘,并完成同步,性能较差但是数据完整性较好
# 2.everysec:默认设置,异步操作,每秒记录,并完成同步,如果1s内宕机,只丢失1s内的数据
# 3.no:always 和 everysec 都会和磁盘保持同步,而 no 表示写入 aof 文件但并不等待磁盘同步,也就是写入缓冲区,Linux 中 30s 后自动同步到磁盘
# appendfsync always
appendfsync everysec
# appendfsync no


# 子进程重写 aof 文件时是否不使用 appendfsync(开启子进程重写),用默认 no 即可,也就是使用,可以保证数据安全性
# 注意这个参数前面有一个 no 了,设置为 yes,才表示不使用 appendfsync,因此这个参数有点绕
# 如果设置为 yes,此时不会写入磁盘,只是写入缓冲区,因此不会造成阻塞。
# 但如果 redis 挂掉,在 linux 系统默认设置下,会丢失 30s 的数据
# 使用 no,表示使用 appendfsync,表示会由子进程重写,这时候和主进程之间会有资源上的竞争,因为都要操作磁盘,所以会有阻塞的情况,但是不会丢失数据。
no-appendfsync-on-rewrite no


# aof 文件增长比例,指当前 aof 文件比上次重写的增长比例大小。
# aof 重写即在 aof 文件在一定大小之后,重新将整个内存写到 aof 文件当中,以反映最新的状态(相当于bgsave)。
# 这样就避免了,aof 文件过大而实际内存数据小的问题 (频繁修改数据问题).
auto-aof-rewrite-percentage 100


# aof 文件重写最小的文件大小,即最开始 aof 文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了(根据上一次重写完成之后的大小)
# 此变量仅初始化启动 redis 有效,如果是 redis 恢复时,则 lastSize 等于初始 aof 文件大小.
auto-aof-rewrite-min-size 64mb


# 指 redis 在恢复时,会忽略最后一条可能存在问题的指令。
# 默认值 yes。即在 aof 写入时,可能存在指令写错的问题(突然断电,写了一半)
# 这种情况下,yes 会继续,而 no 会直接恢复失败,用户必须手动修复 AOF 文件才能正常启动 Redis 服务。
aof-load-truncated yes


# 4.0 开始允许使用 RDB-AOF 混合持久化的方式,结合了两者的优点,通过 aof-use-rdb-preamble 配置项可以打开混合开关
aof-use-rdb-preamble yes

其中比较重要的是 appendfsync 参数,用它来设置 AOF 的持久化策略,可以选择按时间间隔或者操作次数来存储 AOF 文件,这个参数的三个值在文章开头有说明,这里就不再复述了。

数据恢复

1)正常数据恢复

正常情况下,只要开启了 AOF 持久化,并且提供了正常的 appendonly.aof 文件,在 Redis 启动时就会自定加载 AOF 文件并启动。

默认情况下 appendonly.aof 文件在 Redis 的启动目录下,当然我们也可以设置。

持久化文件加载规则

  • 如果只开启了 AOF 持久化,Redis 启动时只会加载 AOF 文件(appendonly.aof),进行数据恢复;
  • 如果只开启了 RDB 持久化,Redis 启动时只会加载 RDB 文件(dump.rdb),进行数据恢复;
  • 如果同时开启了 RDB 和 AOF 持久化,Redis 启动时一样只会加载 AOF 文件(appendonly.aof),进行数据恢复。

在 AOF 开启的情况下,即使 AOF 文件不存在,只有 RDB 文件,也不会加载 RDB 文件。

2)简单异常数据恢复

在 AOF 写入文件时如果服务器崩溃,或者是 AOF 存储已满的情况下,AOF 的最后一条命令可能被截断,这就是异常的 AOF 文件。

在 AOF 文件异常的情况下,如果为修改 Redis 的配置文件,也就是使用 aof-load-truncated等于 yes 的配置,Redis 在启动时会忽略最后一条命令,并顺利启动 Redis。

3)复杂异常数据恢复

AOF 文件可能出现更糟糕的情况,当 AOF 文件不仅被截断,而且中间的命令也被破坏,这个时候再启动 Redis 会提示错误信息并中止运行,错误信息如下:

* Reading the remaining AOF tail...
# Bad file format reading the append only file: make a backup of your AOF file, 
# then use ./redis-check-aof --fix <filename>

出现此类问题的解决方案如下:

  • 首先使用 AOF 修复工具,检测出现的问题,在命令行中输入 redis-check-aof 命令,它会跳转到出现问题的命令行,这个时候可以尝试手动修复此文件;
  • 如果无法手动修复,我们可以使用 redis-check-aof --fix 自动修复 AOF 异常文件,不过执行此命令,可能会导致异常部分至文件末尾的数据全部被丢弃。

AOF 优缺点

AOF 优点:

  • AOF 持久化保存的数据更加完整,AOF 提供了三种保存策略:每次操作保存、每秒钟保存一次、跟随系统的持久化策略保存,其中每秒保存一次,从数据的安全性和性能两方面考虑是一个不错的选择,也是 AOF 默认的策略,即使发生了意外情况,最多只会丢失 1s 钟的数据;
  • AOF 采用的是命令追加的写入方式,所以不会出现文件损坏的问题,即使由于某些意外原因,导致了最后操作的持久化数据写入了一半,也可以通过 redis-check-aof 工具轻松的修复;
  • AOF 持久化文件,非常容易理解和解析,它是把所有 Redis 键值操作命令,以文件的方式存入了磁盘。即使不小心使用 flushall 命令删除了所有键值信息,只要使用 AOF 文件,重启 Redis 即可恢复之前误删的数据,但前提是把 AOF 文件中最后的 flushall 命令删除之后再恢复,否则恢复完之后就又 flushall 了。

AOF 缺点:

  • 对于相同的数据集来说,AOF 文件要大于 RDB 文件;
  • 在 Redis 负载比较高的情况下,RDB 比 AOF 性能更好;
  • RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 更健壮。

AOF 小结

AOF 保存数据更加完整,它可以记录每次 Redis 的键值变化,也就是通过逐一记录操作命令,在恢复时再逐一执行命令的方式,保证了数据的可靠性。但考虑到性能,也可以选择每秒保存一次数据。

AOF 的持久化文件更加易读,但相比与二进制的 RDB 文件来说,所占的存储空间也越大。为了解决这个问题,AOF 提供自动化重写机制,直接根据数据库里数据的最新状态,生成这些数据的插入命令,作为新日志,最大程度的减少了 AOF 占用空间大的问题。这个过程通过后台线程完成,避免了对主线程的阻塞。同时 AOF 也提供了很方便的异常文件恢复命令: redis-check-aof --fix ,为使用 AOF 提供了很好的保障。

混合持久化

我们说 Redis 的持久化有 RDB 和 AOF,这两者的优缺点我们再来简单复习一下:

同步策略:

  • RDB:可以指定某个时间内发生多少个命令进行同步。比如一分钟内发生了两次命令,就进行一次同步。
  • AOF:每秒同步或者每次发生命令后同步。

存储内容:

  • RDB:存储的是 redis 里面具体的值。
  • AOF:存储的是执行的写操作命令。

优点:

  • RDB:1. 存储数据到文件中会进行压缩,文件体积比 aof 文件小。2. 因为存储的是 redis 具体的值,并且会经过压缩,因此在恢复的时候比 aof 文件快,并且适合远距离传输。3. 非常适用于备份。
  • AOF:1. AOF 的策略是每秒钟或者每次发生写操作的时候都会同步,因此即使服务器发生故障,也只会丢失一秒的数据。2. AOF 存储的是 redis 命令并且直接追加到 aof 文件后面,因此每次备份的时候只要添加新的数据进去就可以了。3. 如果 aof 文件比较大,那么 redis 会进行重写,只保留最小的命令集合。

缺点:

  • RDB:1. RDB 是在多少时间内发生了多少写操作的时候就会发出同步机制,虽然采用压缩机制,但 RDB 在同步的时候都重新保存整个 redis 中的数据,因此该操作不宜频繁执行,一般会设置在最少 5 分钟内才保存一次数据。在这种情况下,一旦服务器故障,就会造成 5 分钟的数据丢失。2. 在数据保存进 rdb 文件的时候,redis 会 fork 出一个子进程用来同步,在数据流比较大的时候可能会非常耗时。
  • AOF:1. aof 文件因为没有压缩,因此体积比 rdb 文件大。2. AOF 是在每秒或者每次写操作都进行备份,因此如果并发量比较大,效率会有点低。3. 因为存储的是命令,因此在灾难恢复的时候 redis 会重新运行 aof 文件里的命令,速度不及 RDB。

所以为了能同时使用 RDB 和 AOF 各种的优点,Redis 4.0 之后新增了混合持久化的方式。

在开启混合持久化的情况下,AOF 重写时会把 Redis 的持久化数据,以 RDB 的格式写入到 aof 文件的开头(写入值),之后的数据再以 AOF 的格式追加到文件的末尾(写入命令)。

这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势,颇有 "鱼和熊掌可以兼得" 的感觉,建议在实践中用起来。

开启混合持久化

查询是否开启混合持久化可以使用 config get aof-use-rdb-preamble 命令:

127.0.0.1:6379> config get aof-use-rdb-preamble
1) "aof-use-rdb-preamble"
2) "yes"
127.0.0.1:6379> 

其中 yes 表示已经开启混合持久化,no 表示关闭,Redis 5.0 默认值为 yes。 如果是其他版本的 Redis 首先需要检查一下,是否已经开启了混合持久化,如果关闭的情况下,可以通过以下两种方式开启:

  • 通过命令行开启:config set aof-use-rdb-preamble yes
  • 通过修改 Redis 配置文件开启,将配置文件中的 aof-use-rdb-preamble no 改成 aof-use-rdb-preamble yes。

关于 AOF 和 RDB 的选择问题,有三点建议:

  • 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
  • 如果允许分钟级别的数据丢失,可以只使用 RDB;
  • 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。

数据恢复和源码解析

混合持久化的数据恢复和 AOF 持久化过程是一样的,只需要把 appendonly.aof 放到 Redis 的根目录,在 Redis 启动时,只要开启了 AOF 持久化,Redis 就会自动加载并恢复数据。

1)混合持久化的加载流程

  • 1. 判断是否开启 AOF 持久化,开启继续执行后续流程,未开启执行加载 RDB 文件的流程;
  • 2. 判断 appendonly.aof 文件是否存在,文件存在则执行后续流程;
  • 3. 判断 AOF 文件开头是 RDB 的格式, 先加载 RDB 内容再加载剩余的 AOF 内容;
  • 4. 判断 AOF 文件开头不是 RDB 的格式,直接以 AOF 格式加载整个文件。

2)源码解析

Redis 判断 AOF 文件的开头是否是 RDB 格式的,是通过关键字 REDIS 判断的,RDB 文件的开头一定是 REDIS 关键字开头的,判断源码在 Redis 的 src/aof.c 中,核心代码如下所示:

char sig[5]; /* "REDIS" */
if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
    // AOF 文件开头非 RDB 格式,非混合持久化文件
    if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
} else {
    /* RDB preamble. Pass loading the RDB functions. */
    rio rdb;

    serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
    if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
    rioInitWithFile(&rdb,fp);
    // AOF 文件开头是 RDB 格式,先加载 RDB 再加载 AOF
    if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
        serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
        goto readerr;
    } else {
        serverLog(LL_NOTICE,"Reading the remaining AOF tail...");
    }
}
// 加载 AOF 格式的数据

可以看出 Redis 是通过判断 AOF 文件的开头是否是 REDIS 关键字,来确定此文件是否为混合持久化文件的。

AOF 格式的开头是 *,而 RDB 格式的开头是 REDIS。

优缺点

混合持久化优点:

  • 混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。

混合持久化缺点:

  • AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
  • 兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。

持久化最佳实践

持久化虽然保证了数据不丢失,但同时拖慢了 Redis 的运行速度,那怎么更合理的使用 Redis 的持久化功能呢? Redis 持久化的最佳实践可从以下几个方面考虑。

1)控制持久化开关

使用者可根据实际的业务情况考虑,如果对数据的丢失不敏感的情况下,可考虑关闭 Redis 的持久化,这样所以的键值操作都在内存中,就可以保证最高效率的运行 Redis 了。 持久化关闭操作:

  • 关闭 RDB 持久化,使用命令:config set save ""
  • 关闭 AOF 和 混合持久化,使用命令:config set appendonly no

2)主从部署

使用主从部署,一台用于响应主业务,一台用于数据持久化,这样就可能让 Redis 更加高效的运行。

3)使用混合持久化

混合持久化结合了 RDB 和 AOF 的优点,Redis 5.0 默认是开启的。

4)使用配置更高的机器

Redis 对 CPU 的要求并不高,反而是对内存和磁盘的要求很高(也正因为如此 Redis 在 4.0 之前才是单线程的,当然现在也默认是单线程),因为 Redis 大部分时候都在做读写操作,使用更多的内存和更快的磁盘,对 Redis 性能的提高非常有帮助。

posted @ 2020-07-13 23:41  古明地盆  阅读(7836)  评论(0编辑  收藏  举报