上文介绍了redis的高性能, 本文将继续介绍redis高可靠性, 本文继续讨论redis的高可靠性, 首先介绍两种持久化方式.
Q: redis如何保障高可靠性?
A:
- AOF(Append Only File)日志和RDB(Redis Database)快照持久化机制;
- 采用主从库的容灾机制;
- 哨兵机制;
Q: redis为什么进行持久化?
A: redis作为缓存时, 把后端数据库中的数据存储在内存中, 这样读取数据的速度非常快, 但是一旦服务器宕机,内存中的数据将全部丢失, 因此需要将缓存中数据进行持久化, 通常持久化的方式有两种, AOF和RDB快照;
AOF日志
Q: AOF日志如何实现?
A: 传统的数据库采用的写前日志(Write Ahead Log, WAL), 在实际写数据前,先把修改的数据记到日志文件中,以便故障时进行恢复。AOF 日志正好相反,它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志,如下图所示:
先写内存后记录日志的优点:
- 命令执行后才记录日志,所以不会阻塞当前的写操作。
- 写后日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。可以避免出现记录错误命令的情况
先写内存后记录日志的缺点:
- 如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。
- AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。
Q: AOF日志文件的格式是什么?与RDB快照的日志文件格式区别?
A:
- AOF 则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。
- RDB 将数据库的快照(snapshot)以二进制的方式保存到磁盘中.
Q: AOF三种写回磁盘策略?
A : AOF 机制给我们提供了三个选择,也就是 AOF 配置项 appendfsync 的三个可选值。
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
优缺点:
-
“同步写回”可以做到基本不丢数据,但是它在每一个写命令后都有一个慢速的落盘操作,不可避免地会影响主线程性能;
-
虽然“操作系统控制的写回”在写完缓冲区后,就可以继续执行后续的命令,但是落盘的时机已经不在 Redis 手中了,只要 AOF 记录没有写回磁盘,一旦宕机对应的数据就丢失了;
-
“每秒写回”采用一秒写回一次的频率,避免了“同步写回”的性能开销,虽然减少了对系统性能的影响,但是如果发生宕机,上一秒内未落盘的命令操作仍然会丢失。所以,这只能算是,在避免影响主线程性能和避免数据丢失两者间取了个折中。
-
三种策略的写回时机, 优缺点汇总在了一张表格里:
策略 | 写回时机 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 可靠性高, 数据基本不丢失 | 每个写数据都会落盘, 性能影响较大 |
EverySec | 每秒写会 | 性能适中 | 宕机时丢掉1秒数据 |
No | 操作系统控制写回 | 性能好 | 宕机时丢失数据较多 |
Q: 日志文件太大了怎么办?
A : 原因
- 文件系统本身对文件大小有限制,无法保存过大的文件;
- 如果文件太大,之后再往里面追加命令记录的话,效率也会变低;
- 如果发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到 Redis 的正常使用;
这样需要AOF 重写机制.
Q:什么时AOF重写机制
A: 重写机制具有“多变一”功能。所谓的“多变一”,也就是说,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令。
当一个键值对被多条写命令反复修改时,AOF 文件会记录相应的多条命令。但是,在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了。
Q: AOF 重写会阻塞吗?
A : 和 AOF 日志由主线程写回不同,重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。
**主线程 fork 出后台的 bgrewriteaof 子进程。**此时,fork出的子进程指向与父进程相同的内存地址空间。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
写时复制(Copy On Write, COW), fork瞬间一定是会阻塞主线程的,fork采用操作系统提供的写实复制(Copy On Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题,但fork子进程需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。拷贝内存页表完成后,子进程与父进程指向相同的内存地址空间,也就是说此时虽然产生了子进程,但是并没有申请与父进程相同的内存大小。那什么时候父子进程才会真正内存分离呢?“写实复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据,这个过程中,父进程也可能会产生阻塞的风险,就是下面介绍的场景。
fork出的子进程指向与父进程相同的内存地址空间,此时子进程就可以执行AOF重写,把内存中的所有数据写入到AOF文件中。但是此时父进程依旧是会有流量写入的,如果父进程操作的是一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应的内存数据,申请新的内存空间,这样逐渐地,父子进程内存数据开始分离,父子进程逐渐拥有各自独立的内存空间。因为内存分配是以页为单位进行分配的,默认4k,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产阻塞风险。另外,如果操作系统开启了内存大页机制(Huge Page,页面大小2M),那么父进程申请内存时阻塞的概率将会大大提高,所以在Redis机器上需要关闭Huge Page机制。Redis每次fork生成RDB或AOF重写完成后,都可以在Redis log中看到父进程重新申请了多大的内存空间。
Q: AOF 重写也有一个重写日志,为什么它不共享使用 AOF 本身的日志呢?
A: AOF重写不复用AOF本身的日志,一个原因是父子进程写同一个文件必然会产生竞争问题,控制竞争就意味着会影响父进程的性能。二是如果AOF重写过程中失败了,那么原本的AOF文件相当于被污染了,无法做恢复使用。所以Redis AOF重写一个新文件,重写失败的话,直接删除这个文件就好了,不会对原先的AOF文件产生影响。等重写完成之后,直接替换旧文件即可。
Q: AOF的优缺点?
A:
优点:
- 相比RDB更安全.如果采用always持久化方式, AOF保存的是全部命令集合, 如果宕机, 可重新执行AOF文件中命令,从而获取全量数据,
缺点:
- AOF文件比RDB快照要大, 数据恢复较慢;
- AOF文件太大, 需要重写时, 虽然fork子线程成功后不会阻塞主线程,但是fork时会影响主线程.
RDB快照
Q: RDB持久化方法?
A : RDB(Redis DataBase)是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
Q: RDB保存的数据格式?
A: RDB文件格式为二进制文件, 因此文件相比较AOF来说较小一些.
redis生成快照会阻塞主线程吗?
A : Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
- save:在主线程中执行,会导致阻塞;
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
Q: redis在生成RDB快照时, 数据能修改吗?
A : 能修改; 反证法: 假设内存有4G数据, 磁盘的写入带宽是 0.2GB/s , 这样4G的数据大概需要(4/0.2=20s)20s时间, 如果20s内对4G的数据都不能修改, 无疑会影响redis的性能.
Redis借用了操作系统的写时复制技术(Copy-On-Write, COW), 在执行快照的同时,正常处理写操作。
Q: 什么是写时复制技术?
A : bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
Q: 为了保证数据不丢失, Redis多久执行一次快照合适?
A :
- 首先简单介绍一下RDB自动出发策略
save 900 1
save 300 10
save 60 3600
配置解读:
-
“save 900 1”表示如果900秒内至少1个key发生变化(新增、修改和删除),则重写rdb文件;
-
“save 300 10”表示如果每300秒内至少10个key发生变化(新增、修改和删除),则重写rdb文件;
-
“save 60 3600”表示如果每60秒内至少10000个key发生变化(新增、修改和删除),则重写rdb文件。
- 其次, 如果生成RDB快照文件太频繁, 例如每秒钟生成一次, 这样一旦宕机, 数据丢失会少, 但是生成快照本身也有其开销:
- bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了;
- 如果频繁将数据写入磁盘, 又回给磁盘带来非常大的压力, 多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
这样就会有一个矛盾点, 快生成太快会造成额外的开销, 生成太慢,一旦宕机又会丢失数据. Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
Q: RDB的优缺点?
A :
优点:
- 对于大的数据集来说, RDB快照文件的格式为二进制流, 相比协议文本格式AOF文件来说更小, 宕机后重启的速度更快;
- RDB : 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能; 而AOF持久化直接在主线程中进行的, 会影响主进程效率;
缺点:
- 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
- fork时为了保持主子一致, 通常会阻塞主进程, 这样会影响性能;
小结
- 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;如果- - 允许分钟级别的数据丢失,可以只使用 RDB;
- 如果只用 AOF,**优先使用 everysec **的配置选项,因为它在可靠性和性能之间取了一个平衡。