我们熟知redis是内存数据库,它将自己的数据存储在内存里面,如果如图redis进程退出或突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。
Redis 的持久化机制有三种,第一种是快照(RDB),第二种是 AOF 日志,第三种是混合持久化。快照是一次全量备份,AOF 日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。混合持久化综合了上面两种的优点。
RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
触发机制:
除了执行命令手动触发之外,redis内部还存在自动触发RDB的持久化机制,如下
bgsvae运作流程如下
至此RDB的生成过程大概讲完了,我们知道Redis为了不阻塞主线程,调用 glibc 的函数fork产生一个子进程来生成RDB备份文件,试想一个问题,如果一个大型的 hash 字典正在持久化,结果一个请求过来把它给删掉了,还没持久化完呢,这尼玛要怎么搞??
Copy On Write
Redis 使用操作系统的多进程 COW(Copy On Write) 机制来实现快照持久化。子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段.这是 Linux 操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来。在进程分离的一瞬间,内存的增长几乎没有明显变化。子进程做数据持久化,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。但是父进程不一样,它必须持续服务客户端请求,然后对内存数据结构进行不间断的修改。
这个时候就会使用操作系统的 COW 机制来进行数据段页面的分离。数据段是由很多操作系统的页面组合而成(一页4k),当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改。这时子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据。
子进程因为数据没有变化,它能看到的内存里的数据在进程产生的一瞬间就凝固了,再也不会改变,这也是为什么 Redis 的持久化叫「快照」的原因。接下来子进程就可以非常安心的遍历数据了进行序列化写磁盘了。
RDB的载入
和使用save或者bgsave命令不同,RDB的载入是在服务器启动的时候自动执行的,所以Redis并没有专门用于载入RDB的文件命令。值得一提的是:
RDB文件的处理
保存:RDB文件保存在dir配置的指定目录下,文件名通过dbfilename配置指定。可以通过执行 config set dir{new Dir} 和 config set dbfilename{newFileName} 运行期动态执行。
压缩:redis 默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远小于内存大小,默认开启,可以通过参数config set rdbcompression{yes|no}动态修改
RDB的优缺点
优点:
缺点:
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
AOF的主要作用是解决了数据持久化的实时性,AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。
Redis 会在收到客户端修改指令后,进行参数校验进行逻辑处理后,如果没问题,就立即将该指令文本存储到 AOF 日志中,也就是先执行指令才将日志存盘。这点不同于mysql、hbase等存储引擎,它们都是先存储日志再做逻辑处理。(所以单机redis不能做到一条数据也不丢失)
使用AOF
开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同RDB持久化方式一致,通过dir配置指定。AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)。
命令写入and文件同步
服务器在执行完一个写命令后(如 set k v,lpush k v 等),会把写入命令会追加到aof_buf(缓冲区)中。后续防止丢失aof_buf中的数据,在调用linux的glibc提供的fsync函数将aof_buf中的数据强制刷新到磁盘。
AOF为什么把命令追加到aof_buf中?Redis使用单线程响应命令,如果每次写AOF文件命令都直接追加到硬盘(调用fsync),那么性能完全取决于当前硬盘负载。先写入缓冲区aof_buf中,还有另一个好处,Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡。
Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制,不同值的含义如下
系统调用write和fsync说明:
AOF重写机制
我们知道AOF持久化是通过保存被执行的写命令来实现的,随着命令不断写入AOF,文件会越来越大。为了解决这个问题,Redis引入AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF
重写后为什么变小?
AOF重写触发条件
手动触发:直接调用bgrewriteaof命令。
自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
自动触发条件:aof_current_size > auto-aof-rewrite-min-size && (x+1)*aof_base_size
其中aof_current_size和aof_base_size可以在info Persistence统计信息中查看。
AOF重写Redis做了哪些事情呢?
AOF重写功能作为一个辅助功能,redis肯定不希望阻塞主进程的执行,所以redis把AOF重写放到一个子进程去执行。上文讲到frok运用cow(写时复制技术)技术,所以子进程只能看到fork那一瞬间产生的镜像数据。为了解决这一个问题redis设置了一个 AOF重写缓存区(aof_rewrite_bug) 用来存储AOF重写期间产生的命令,等子进程重写完成后通知父进程,父进程把重写缓存区的数据追加到新的AOF文件(注:这里值得注意,AOF重写期间如果有大量的写入,父进程在把aof_rewrite_buf写到新的aof文件时会造成大量的写盘操作,会造成性能的下降,redis 4.0以后增加管道机制来优化这里(把aof_rewrite_buf追加工作交给子进程去做),感兴趣的同学可以自行查阅)
所以说在AOF重写期间主进程做了下面几件事
值得注意的是,redis每次重写写入磁盘的大小由aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞
过程如图:
我们知道AOF重写期间会消耗大量磁盘IO,可以开启配置no-appendfsync-on-rewrite yes,表示在AOF重写期间不做fsync操作,默认为no。但是如此一来某些情况下会丢掉重写期间的所有数据。慎重啊,铁子
重启加载
讲RDB时提到,如果开启AOF优先加载AOF文件,否则执行RDB文件
AOF追加阻塞
当开启AOF持久化时,默认以及常用的同步硬盘策略是everysec(每s一刷),对于这种方式,Redis使用另一个线程每s执行fsync同步磁盘。试想一个问题,假设硬盘资源繁忙,fsync刷盘缓慢,主线程该如何做?
主线程写入AOF缓冲区后会对比上次AOF同步时间
发现两个问题:
RDB-AOF混合持久化(redis 4.0+提供)
细细想来aofrewrite时也是先写一份全量数据到新AOF文件中再追加增量只不过全量数据是以redis命令的格式写入。那么是否可以先以RDB格式写入全量数据再追加增量日志呢这样既可以提高aofrewrite和恢复速度也可以减少文件大小还可以保证数据的完毕性整合RDB和AOF的优点那么现在4.0实现了这一特性——RDB-AOF混合持久化。
综上所述RDB-AOF混合持久化体现在aofrewrite时,即在AOF重写时把frok的那个镜像写成RDB,后续AOF重写缓冲里的数据继续追加到该文件中。
配置为 aof-use-rdb-preamble no #默认关闭,yes 打开
小结:
文章涉及到的命令及配置: