前两天晚上下班的时候,和朋友走在路上聊起了Redis的主从复制与哨兵模式。突然觉得自己对这一块的知识还没有一个比较系统化的了解。所以,现在就开始仔细学习一下相关的知识,整理一下Redis相关的知识点,这里做个记录,同时也希望能帮助到大家。
CentOS 7.6.1810 Redis 5.0.5
这里,我将简单记录下Redis安装的一些关键命令,如果还有不清楚地地方,请大家自行谷歌~
# 编译Redis
cd redis-5.0.5
make
# 编译安装
cd src
make install PREFIX=/home/tom/data/redis-5.0.5
# 将配置文件移动过去
mv redis.conf ../redis-5.0.5/redis.conf
# 删除原来的编译包(可选)
rm -rf redis-5.0
然后,修改部分配置项:
# 支持外部访问
bind 0.0.0.0
# 后台运行
daemonize yes
# 日志路径
logfile "logs/redis-6397.log"
# 密码
requirepass 123456
保存配置,启动Redis:
cd redis-5.0.5
mkdir logs
./bin/redis-server redis.conf
至此,Redis安装,并配置启动完毕。
我们都知道,Redis是一款内存数据库,不管是系统宕机,还是Redis进程退出,内存中的数据都会丢失。如果说,我们仅仅将Redis用作项目的缓存,而不会涉及到任务业务数据的存储时,我们可能并不需要将Redis中的数据持久化。但是,如果我们是将业务中的热点数据放到Redis,这时候,数据丢失就是大问题了。为了解决这个问题,Redis为我们提供了两种不同的持久化选项:快照(RDB)和追加(AOF)。下面我参考着官方文档,加上我的理解,详细的介绍下这两种持久化方式。
默认情况下,Redis将数据集的快照写入一个名叫dump.rdb 的文件中,保存在磁盘上。这个配置在redis.conf配置文件中:
dbfilename dump.rdb
当然,这个文件的名称与路径,我们都是可以自定义的。修改配置中的路径即可。
下面,介绍下开启RDB快照持久化的三种方式。
SAVE命令将会执行一次 同步 的数据集持久化操作,将当前时间点Redis实例内所有的数据都写入到一个RDB格式的文件中。因为这是一个同步的操作,所以,在数据集同步期间,服务端将会阻塞其他客户端的读、写请求,直至服务端数据集备份完毕。
由于SAVE命令每次都会执行一次全备份,如果数据集比较大的情况下,这个备份操作将会使得服务端在相当长的一段时间内无法响应客户端的请求。所以,一般情况下,不要在生产环境直接使用SAVE命令!
同时,在SAVE命令的官方文档中,提到这么一点:如果出现了系统阻止Redis创建子进程的问题(例如fork(2)系统调用中的错误),SAVE命令可能是执行最新数据转储的最后手段。这里fork(2)系统调用错误,有兴趣的朋友可以自行去了解~
另外,补充一点,在SAVE命令执行完毕后,如果执行成功,该命令将会返回“OK”这个字符串。
与上面提到的SAVE命令不同,BGSAVE将执行一次 异步 持久化操作。当客户端执行BGSAVE命令时,服务端将会fork一个子进程去进行数据集持久化,同时,父进程将继续为客户端服务。当数据持久化完毕时,子进程会自己退出。
需要注意的是,这个fork操作,仍然是一个同步操作,如果数据集比较大,那么fork进程这个操作过程可能耗时也比较大,在这段时间内,服务端仍然会阻塞客户端的读、写请求。数据集比较大时,可能导致Redis在毫秒级的时间内无法响应客户端的请求。如果数据集特别大同时CPU的性能又不好的情况下,这个情况可能会持续1秒。当然,一般情况下,fork()是很快的。
当我看到上面这一段的官方文档的时候,我非常疑惑:fork()操作的耗时,为什么和Redis实例的数据集的大小有关系?这个问题,在问题解决中我会详细谈到。
关于BGSAVE,还需要补充一些点。对于SAVE命令,上文我们提到,它会等到持久化操作执行完毕后,才会返回“OK”字符串标识。而BGSAVE不同,它会立即返回“OK”标识,无论持久化操作实际成功与否。我们可以通过 LASTSAVE 命令,获取上一次成功了的持久化操作完后时的时间戳,以此来判断我们上一次BGSAVE操作是否成功完成。
除了上面两种通过命令手动持久化的方式外,我们还可以通过在Redis.conf中配置策略,让服务端在指定条件下自动开启持久化备份操作。Redis 5.0.5中默认的配置如下:
save 900 1
save 300 10
save 60 10000
该如何理解呢?比如,对于【save 900 1】这个配置项来说,其意义是:每900秒,如果在这段时间内,至少有1个key-value键值对“被操作”过,那么就执行一次持久化操作。这里,我所说的“被操作”,包含以下几种情况:
通过配置开启持久化的这种方式,其工作方式和BGSAVE类似,服务端也会先fork()出一个子线进程,然后由子进程去进行持久化操作。
如果我们注释掉所有的配置,或者将配置改为 【save ""】,那么就相当于我们停用了 通过配置去持久化 这项功能。
工作方式
针对通过 BGSAVE 和 配置 这两种方式进行的持久化操作,服务器执行以下操作:
而对于 SAVE 命令,除了第一步fork()子进程,后两步操作一样。
也就是说,无论何时,复制RDB文件都是安全的。 RDB 文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用 rename(2) 原子地用临时文件替换原来的 RDB 文件。
后台持久化失败策略
从redis.conf中作者的注释可以得到,在默认情况下,如果我们通过配置文件,配置了至少一种持久化策略,当后台持久化操作执行失败时,Redis将停止接受写入操作!这样做主要是为了让客户端端意识到,数据不能正确持久化到磁盘,否则可能出现灾难性的后果。当后台持久化进程又重新能正常工作时,Redis将会自动开始接受新的写入操作。当然,我们可以通过配置,停用这种机制,这样,即使后台持久化失败,Redis也将继续接受新的写入请求。默认该策略开启:
stop-writes-on-bgsave-error yes
String对象压缩
此外,RDB文件还支持“字符串”压缩。该配置项默认开启:
rdbcompression yes
该配置项的意义是,在转储 .rdb 文件的时候,对可压缩的对象使用LZF算法对String对象进行压缩。Redis作者也建议将该配置打开。
Checksum标记校验
从Redis 5.0开始,在RDB文件的末尾,将会防止一个CRC64的Checksum标记,这个主要是为了检查文件是否被损坏。当然,这个操作将会造成在持久化或恢复的时候有更多的性能损耗(大约10%)。该功能默认打开:
rdbchecksum yes
当然,我们也可以选择将该功能关闭。当我们关闭时,在持久化的时候,checksum位将会变为一个“零值”,这样在从RDB文件恢复的时候,将会跳过Checksum校验。
上面我说了将数据集快照写入RDB文件的持久化方式,这种方式可能存在数据丢失的情况。比如,我们配置了 【save 30 1】,那就可能出现几种极端,在某一次快照保存完毕后,下一次同步正准备开始时,Redis服务突然被停掉了,那么,这30秒内的发生改变的数据就会丢失。
从 1.1 版本开始,Redis新增了一种更可靠的持久化方式:AOF。当我们开启AOF持久化的时候,每当Redis执行一次数据集更改操作的命令,这些命令将会 异步 追加到AOF文件的末尾。当Redis重启时,这些将会被执行,重建之前的数据集。这样,即使出现系统宕机,或者Redis服务挂掉等问题,丢失的只是最近一次写入或者最近一秒(不同策略)的数据。
默认情况下,AOF模式是关闭的,因为RDB快照已经能够满足绝大多数应用的需求。我们可以通过配置文件,开启AOF持久化:
# 是否打开AOF模式,默认是 no,关闭
appendonly no
# .aof文件的路径、文件名
appendfilename "appendonly.aof"
下面,我们介绍下AOF模式的三种持久化策略:
# appendfsync always
appendfsync everysec
# appendfsync no
每一次写入操作的命令,都会追加到AOF文件的末尾。这个写入操作是通过fsync()函数进行的,这是一个同步操作。
这种策略是最安全的,但是每一次追加操作都会存在fsync() IO操作,虽然安全,但是很费时间。
默认的策略,每一秒将前一秒新增的数据追加到AOF文件的末尾。这个操作也是通过fsync()函数进行的。
极端情况下,可能丢失1秒的数据。这种策略是在 安全性 和 效率之间的一个折中方案。
当我们使用 no 这种策略时,将不会调用 fsync()函数,而是由 操作系统决定何时 flush数据缓冲区。这种策略更快,但相对而言,可能丢失的数据也就变成了不确定。在Redis.conf文件中,作者在注释中写到:
if you can live with the idea of some data loss consider the default persistence mode that's snapshotting。
如果我们能接受一定数据的丢失,我们应该考虑 快照(RDB)持久化的方式。
因为AOF总是不断将新的命令追加到AOF文件的末尾,随着写操作的越来越多,AOF文件也会越来越大。比如,我们对一个key执行100次 INCR 命令,AOF文件中实际记录了每一次执行的命令,但是实际上,对我们来说,有用的是最后一次记录的值。
为了解决这个问题,Redis支持在后端重建AOF文件,而不会中断接受客户端的请求。
日志重写原理
开启日志重写的两种方式:
一、BGREWRITEAOF命令:
当客户端手动执行 BGREWRITEAOF 命令时,Redis 将会开启一个AOF重写进程,重写一个 包含重建当前内存中数据集所需的最短命令序列 的临时文件。如果这次重写操作执行失败,也不会有数据丢失,原始的AOF文件不会受影响。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
需要注意的是,在默认情况下,只有当后台没有进程正在执行持久化操作时,AOF重写操作才会被触发。特别要注意:
这个功能,我也可以通过配置修改其策略:
no-appendfsync-on-rewrite no
默认情况下,该配置项的值为“no”。这个配置项的名称总是给人一种错觉,如果命名成【on-rewrite-appendfsync-no】这样,我相信大家一下子就明白了。这配置项什么意思呢?当我们将该配置项置为 yes 时,如果存在其他子进程在进行持久化操作时,上文提到的AOF策略就相当于被置为了 “appendfsync no”,此时,主进程接受到操作将会写入到AOF缓冲区,而不是直接写入AOF文件中,而后台的持久化操作继续进行IO操作,写入文件;当我们将该配置项置为 no 时,如果存在其他子进程在进行持久化操作,该配置项就会阻止Redis调用 fsync()函数,AOF重写操作就会等待,或者直接被拒绝。
二、服务端自动重写
从Redis 2.4版本开始,Redis支持当AOF文件 大小增量达到一定的百分比时,自动调用BGREWRITEAOF 命令进行AOF文件重写。下面是其默认配置:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
其工作机制是,在最近一次AOF重写后,Redis会记住AOF文件的大小(如果重启后没有发生AOF重写,则使用启动时AOF文件的大小。),并以此大小作为基准,和当前的文件大小作比较,如果当前的文件比基准大小 大于 了指定的百分比,则会触发重写。此外,我们还需要为要重写的AOF文件指定最小大小,这样,即使达到了百分比增长,但AOF仍然小于指定最小大小时,则不会触发AOF重写。
对于默认配置,其意义就很明了了:当AOF文件的大小增长量达到原始大小的100%,并且AOF文件的大小大于64MB时,就会触发AOF重写。比如:
这里,不必纠结于临界值的处理,因为Redis并不可能实时地去监控AOF文件的大小。
需要注意的是,当我们将auto-aof-rewrite-percentage配置项的值置为 0 时,我们就关闭了自动AOF重写这个功能。
日志重写的好处是,压缩了AOF文件,减少了磁盘占用量,因为将AOF文件中存在的命令压缩为了最小命令集,所以也提高了数据恢复的速度。
在运行Redis的系统崩溃时,可能出现AOF文件损坏的情况。此时,在Redis启动过程中,当AOF数据被加载回内存时,可能会发现AOF文件在末尾被截断。注意,这里是运行Redis的系统崩溃。当Redis本身崩溃或中止,但操作系统仍然正常工作时,这种情况不会发生。
当出现这种情况,Redis本身提供两种机制:
这两种机制,由下面这个配置项进行控制:
aof-load-truncated yes
默认情况下,该配置项的值被置为 yes。在这种情况下,如果检测到AOF文件损坏,Redis会尝试尽可能多地加载AOF文件中的信息,并且输出相关的日志,用来告知客户端该事件;如果该配置的值被修改为 no,那么当AOF文件损坏时,服务将拒绝启动,并出现错误。此时,用户必须在重启Redis服务之前使用“redis-check-aof”程序修复AOF文件。在安装完Redis后,redis-check-aof可执行文件 和 redis-server 在同一个目录下 redis-5.0.5/bin 目录下。
redis-check-aof -fix appendonly.aof
这里,需要补充说明的是:如果发现AOF文件在中间损坏,服务器仍然会退出,并出现错误。只有当Redis试图从AOF文件中读取更多数据,但找不到足够的字节时,aof-load-truncated 选项才适用。
重写AOF文件时,Redis能够在AOF文件中使用RDB作为前导,以便更快地重写和恢复。打开此选项时,重写的AOF文件由两个不同的小节组成:[RDB文件][AOF尾部]。这个功能,默认是打开的:
aof-use-rdb-preamble yes
在加载AOF文件的时候,如果Redis识别到AOF文件是以“REDIS”字符串开头的(读取AOF文件的前5个字符),那么说明这是一个混合持久化的AOF文件。正确的RDB文件一定是以“REDIS”字符串开头,而纯AOF文件是以“*”开头的。在此时,就会去读取RDB文件,直到遇到“RDB_OPCODE_EOF”结束标记,此时再以AOF格式解析剩下的文件内容,直到整个加载过程完成。
默认情况下,Redis 5.0.5版本中,只开启了RDB快照持久化,而没有开启AOF。当然,我们通过配置,将两种持久化方式都打开。在这种情况下,当Redis重启恢复数据时,会优先恢复AOF文件中的数据,因为默认情况下,AOF的数据总是更加完整。
官方文档原文:
The AOF log is an append only log, so there are no seeks, nor corruption problems if there is a power outage. Even if the log ends with an half-written command for some reason (disk full or other reasons) the redis-check-aof tool is able to fix it easily.
1、Redis实例的数据集的大小为什么对BGSAVE进行fork()子进程操作的耗时有影响?
fork()操作,会创建一个子进程,而这个子进程是与父进程完全相同的。这就意味着,父进程的所有状态都会被复制,包括打开的文件、寄存器的状态和所有的内存分配,其中也包括程序代码。所以,当数据集太大的时候,这部分数据也会被复制到子进程,这时候,fork()操作就会变慢。
至此,Redis持久化相关的知识就总结完毕了。如果有什么地方我没有描述清楚的,欢迎大家留言一起探讨。
1、https://redis.io/topics/persistence
2、http://www.redis.cn/topics/persistence.html
3、redis.conf
4、http://oldblog.antirez.com/post/redis-persistence-demystified.html
5、https://www.toutiao.com/a6720120047622160910/
6、https://stackoverflow.com/questions/2731531/faster-forking-of-large-processes-on-linux
7、https://blog.csdn.net/jingkyks/article/details/46956905
8、https://yq.aliyun.com/articles/193034
9、https://www.bottomupcs.com/fork_and_exec.xhtml