写在前
- Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。有字符串,链表,集合和有序集合。支持在服务器端计算集合的并,交和补集(difference)等,还支持多种排序功能。所以Redis也可以被看成是一个数据结构服务器。
- Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”);也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。
- 很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。
Redis如何持久化?
RDB方式持久化
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,RDB文件是一个经过压缩的二进制文件。RDB是Redis默认的持久化方式,会在对应的目录下生产一个dump.rdb文件,重启会通过加载dump.rdb文件恢复数据。
优点:
(1)整个redis数据库中只有一个文件dump.rdb,方便持久化;
(2)性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能) ;
(3)如果数据集偏大,RDB的启动效率会比AOF更高。
缺点:
(1)数据安全性低。(RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不是特别严格的时候)
(2) 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF方式持久化
AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
优点:
(1)数据安全性更高(即数据持久性,解决了数据不一致的问题),redis.conf中的appendfysnc是对redis性能有重要影响的参数之一。可取三种值(同步策略):always、everysec和no。
- 设置为always时,会极大消弱Redis的性能,因为这种模式下每次write后都会调用fsync(Linux为调用fdatasync)。
- 如果设置为no,则write后不会有fsync调用,由操作系统自动调度刷磁盘,性能是最好的。
- everysec为最多每秒调用一次fsync(每秒同步也是异步完成的,出现宕机,这一秒数据也将丢失),这种模式性能并不是很糟糕(性能还不错),一般也不会产生毛刺,这归功于Redis引入了BIO线程,所有fsync操作都异步交给了BIO线程。
(2)通过append模式写文件(保存了之前已经存在的内容),即使中途服务器宕机,也不会破坏已经存在的内容,可以在下一次Redis启动之前,通过redis-check-aof工具解决数据一致性问题。
(3)如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行(保证数据一致性)。因此在进行rewrite切换时可以更好的保证数据安全性。
缺点:
(1)AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比RDB启动效率低。
(2)根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的
效率是比较高的,同步禁用策略(no)的效率和RDB一样高效。
二者选择的标准:,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。
总之一句话:性能优先,选rdb;安全性优先(缓存一致性),选aof。
常用的配置
RDB持久化配置:Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。 save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。 save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
AOF持久化配置,在Redis的配置文件中存在三种同步方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件。 appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。 appendfsync no #从不同步。高效但是数据不会被持久化(数据不一致)。
RDB持久化的原理
RDB 持久化是把当前进程数据生成快照保存到硬盘的过程,有两个Redis命令可以用于生成RDB文件, 分别对应 save 和 bgsave 命令:
- save:阻塞当前 Redis 服务器,直到 RDB持久化过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。
- bgasve:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。bgsave 是针对 save 阻塞问题做的优化,因此 Redis 内部所有涉及 RDB 的操作都采用 bgsave 的方式,而 save 方式已经废弃。
bgsave 的原理?
原理(执行流程):
① 执行 bgsave 命令,Redis 父进程判断当前是否存在正在执行的子进程(如 RDB/AOF 子进程),如果存在 bgsave 命令直接返回。
② 父进程执行 fork 操作创建子进程,fork 操作过程中父进程会阻塞。
③ 父进程 fork 完成后,bgsave 命令返回并不再阻塞父进程,可以继续响应其他命令。
④ 子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。
⑤ 进程发送信号给父进程表示完成,父进程更新统计信息。
会不会复制多一倍内存?
不会复制两倍内存。主进程fork()子进程之后,内核把主进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向主进程。这也就是共享了主进程的内存,当其中某个进程写内存时(这里肯定是主进程写,因为子进程只负责rdb文件持久化工作,不参与客户端的请求),CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入内核的一个中断例程。中断例程中,内核就会把触发的异常的页复制一份(这里仅仅复制异常页,也就是所修改的那个数据页,而不是内存中的全部数据),于是主子进程各自持有独立的一份。
一句话总结,子进程共享主进程的物理空间,当主子进程有内存写入操作时,read-only内存页发生中断,将触发异常的内存页复制一份(其余的页还是共享主进程的)。
AOF 持久化的原理
AOF 持久化以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性,目前是 Redis 持久化的主流方式。开启 AOF 功能需要设置:appendonly yes,默认不开启。保存路径同 RDB 方式一致,通过 dir 配置指定。
AOF 的工作流程操作(命令写入 append -> 文件同步 sync -> 文件重写 rewrite -> 重启加载 load):
① 所有的写入命令会追加到 aof_buf 缓冲区中。
② AOF 缓冲区根据对应的同步策略向硬盘做同步操作。
③ 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写rewrite,达到压缩的目的。
④ 当服务器重启时,可以加载 AOF 文件进行数据恢复
AOF 命令写入的原理?
AOF 命令写入的内容直接是文本协议格式,采用文本协议格式的原因:
- 文本协议具有很好的兼容性。
- 开启 AOF 后所有写入命令都包含追加操作,直接采用协议格式避免了二次处理开销。
- 文本协议具有可读性,方便直接修改和处理。
AOF 把命令追加到缓冲区的原因:
- Redis 使用单线程响应命令,如果每次写 AOF 文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。
- 先写入缓冲区中还有另一个好处,Redis 可以提供多种缓冲区同步硬盘策略,在性能和安全性方面做出平衡。
为什么Redis先执行指令,再记录AOF日志,而不是像其它存储引擎一样反过来呢?
在常见的数据库中,持久化重做日志一般是先写日志再修改数据库,保证数据/操作不会丢失。所以看到redis的AOF日志的机制后,很困惑
原因:由于AOF文件会比较大,为了避免写入无效指令(错误指令),必须先做指令检查?如何检查,只能先执行了。因为语法级别检查并不能保证指令的有效性,比如删除一个不存在的key。
而MySQL这种是因为它本身就维护了所有的表的信息,所以可以语法检查后过滤掉大部分无效指令直接记录日志,然后再执行。
AOF 文件重写的原理?
文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程,这个新的AOF⽂件和原有的AOF⽂件所保存的数据库状态⼀样,但体积更⼩。可以降低文件占用空间,更小的文件可以更快地被加载。
重写后 AOF 文件变小的原因:
- 丢弃超时的数据:进程内已经超时的数据不再写入文件。
- 无效的AOF命令:旧的 AOF 文件含有无效命令,重写使用进程内数据直接生成,这样新的 AOF 文件只保留最终数据写入命令。
- 写命令合并:多条写命令可以合并为一个,为了防止单条命令过大造成客户端缓冲区溢出,对于 list、set、hash、zset 等类型操作,以 64 个元素为界拆分为多条。
重写流程:
① 执行 AOF 重写请求,如果当前进程正在执行 AOF 重写,请求不执行并返回,如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行。
② 父进程执行 fork 创建子进程,开销等同于 bgsave 过程。
③ 父进程 fork 操作完成后继续响应其他命令,所有修改命令依然写入 AOF 缓冲区并同步到硬盘,保证原有 AOF 机制正确性。
④ 子进程根据内存快照,按命令合并规则写入到新的 AOF 文件。每次批量写入数据量默认为 32 MB,防止单次刷盘数据过多造成阻塞。
⑤ 新 AOF 文件写入完成后,子进程发送信号给父进程,父进程更新统计信息。
⑥ 父进程把 AOF 重写缓冲区的数据写入到新的 AOF 文件并替换旧文件,完成重写。
redis 重启加载的顺序?
AOF 和 RDB 文件都可以用于服务器重启时的数据恢复。Redis 持久化文件的加载流程:
① AOF 持久化开启且存在 AOF 文件时,即优先加载 AOF文件。因为AOF中数据全,性能影响小。
② AOF 关闭时且存在 RDB 文件时,加载RDB 文件。
③ 加载 AOF/RDB 文件成功后,Redis启动成功。
④ AOF/RDB 文件存在错误导致加载失败时,Redis启动失败并打印错误信息。
注意:经过测试,如果 aof不存在(手动删除aof文件),貌似会创建一个新的 空的 aof文件,并没有去用 rdb文件
这是咋回事?加载的顺序,取决于你的配置文件是否开启了aof
,而不是说去判断aof文件是否存在!!所以配置很重要!!流程图如下:
redis加载顺序
拓展:Redis 4.0 对于持久化机制的优化
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点: 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
应用场景对比
- rdb适合大规模的数据恢复,由于rdb是以快照的形式持久化数据,恢复的数据快,在一定的时间备份一次,
- 而aof的保证数据更加完整,损失的数据只在秒内。
具体哪种更适合生产,在官方的建议中两种持久化机制同时开启,如果两种机制同时开启,优先使用aof持久化机制。