Appearance
数据持久化有那些坑?
Redis 的数据持久化,分为 RDB 和 AOF 两种方式。
其中,RDB 是数据快照,而 AOF 会记录每一个写命令到日志文件中。
在数据持久化方面发生问题,主要也集中在这两大块,我们依次来看。
1、master 宕机,slave数据也丢失了?
如果你的 Redis 采用如下模式部署,就会发生数据丢失的问题:
- master-slave + 哨兵部署实例
- master 没有开启数据持久化功能
- Redis 进程使用 supervisor 管理,并配置为「进程宕机,自动重启」
如果此时 master 宕机,就会导致下面的问题:
- master 宕机,哨兵还未发起切换,此时 master 进程立即被 supervisor 自动拉起
- 但 master 没有开启任何数据持久化,启动后是一个「空」实例
- 此时 slave 为了与 master 保持一致,它会自动「清空」实例中的所有数据,slave 也变成了一个「空」实例
在这个场景下,master / slave 的数据就全部丢失了。
所以,一定要避免这种情况发生,建议是:
- Redis 实例不使用进程管理工具自动拉起
- master 宕机后,让哨兵发起切换,把 slave 提升为 master
- 切换完成后,再重启 master,让其退化成 slave
2、AOF everysec 真的不会阻塞主线程吗?
当 Redis 开启 AOF 时,需要配置 AOF 的刷盘策略。
基于性能和数据安全的平衡,你肯定会采用 appendfsync everysec 这种方案。
这种方案的工作模式为,Redis 的后台线程每间隔 1 秒,就把 AOF page cache 的数据,刷到磁盘(fsync)上。
这种方案的优势在于,把 AOF 刷盘的耗时操作,放到了后台线程中去执行,避免了对主线程的影响。
但真的不会影响主线程吗?
其实存在这样一种场景:Redis 后台线程在执行 AOF page cache 刷盘(fysnc)时,如果此时磁盘 IO 负载过高,那么调用 fsync 就会被阻塞住。
此时,主线程仍然接收写请求进来,那么此时的主线程会先判断,上一次后台线程是否已刷盘成功。
后台线程在刷盘成功后,都会记录刷盘的时间。
主线程会根据这个时间来判断,距离上一次刷盘已经过去多久了。整个流程是这样的:
- 主线程在写 AOF page cache(write系统调用)前,先检查后台 fsync 是否已完成?
- fsync 已完成,主线程直接写 AOF page cache
- fsync 未完成,则检查距离上次 fsync 过去多久?
- 如果距离上次 fysnc 成功在 2 秒内,那么主线程会直接返回,不写 AOF page cache
- 如果距离上次 fysnc 成功超过了 2 秒,那主线程会强制写 AOF page cache(write系统调用)
- 由于磁盘 IO 负载过高,此时,后台线程 fynsc 会发生阻塞,那主线程在写 AOF page cache 时,也会发生阻塞等待(操作同一个 fd,fsync 和 write 是互斥的,一方必须等另一方成功才可以继续执行,否则阻塞等待)
产生这个问题的重点在于,磁盘 IO 负载过高导致 fynsc 阻塞,进而导致主线程写 AOF page cache 也发生阻塞。
所以,一定要保证磁盘有充足的 IO 资源,避免这个问题。
3、AOF everysec 真的只会丢失 1 秒数据?
主线程在写 AOF page cache 时,会先判断上一次 fsync 成功的时间,如果距离上次 fysnc 成功在 2 秒内,那么主线程会直接返回,不再写 AOF page cache。
这就意味着,后台线程在执行 fsync 刷盘时,主线程最多等待 2 秒不会写 AOF page cache。
我们继续分析,Redis 主线程为什么要等待 2 秒不写 AOF page cache 呢?
其实,Redis AOF 配置为 appendfsync everysec 时,正常来讲,后台线程每隔 1 秒执行一次 fsync 刷盘,如果磁盘资源充足,是不会被阻塞住的。
也就是说,Redis 主线程其实根本不用关心后台线程是否刷盘成功,只要无脑写 AOF page cache 即可。
但是,Redis 作者考虑到,如果此时的磁盘 IO 资源比较紧张,那么后台线程 fsync 就有概率发生阻塞风险。
所以,Redis 作者在主线程写 AOF page cache 之前,先检查一下距离上一次 fsync 成功的时间,如果大于 1 秒没有成功,那么主线程此时就能知道,fsync 可能阻塞了。
所以,主线程会等待 2 秒不写 AOF page cache,其目的在于:
- 降低主线程阻塞的风险(如果无脑写 AOF page cache,主线程则会立即阻塞住)
- 如果 fsync 阻塞,主线程就会给后台线程留出 1 秒的时间,等待 fsync 成功
但代价就是,如果此时发生宕机,AOF 丢失的就是 2 秒的数据,而不是 1 秒。
这个方案应该是 Redis 作者对性能和数据安全性的进一步权衡。
无论如何,这里你只需要知道的是,即使 AOF 配置为每秒刷盘,在发生上述极端情况时,AOF 丢失的数据其实是 2 秒。
4、RDB 和 AOF rewrite 时,Redis 发生 OOM?
Redis 在做 RDB 快照和 AOF rewrite 时,会采用创建子进程的方式,把实例中的数据持久化到磁盘上。
创建子进程,会调用操作系统的 fork 函数。
fork 执行完成后,父进程和子进程会同时共享同一份内存数据。
但此时的主进程依旧是可以接收写请求的,而进来的写请求,会采用 Copy On Write(写时复制)的方式操作内存数据。
也就是说,主进程一旦有数据需要修改,Redis 并不会直接修改现有内存中的数据,而是先将这块内存数据拷贝出来,再修改这块新内存的数据,这就是所谓的「写时复制」。
如果父进程要修改一个 key,就需要拷贝原有的内存数据,到新内存中,这个过程涉及到了「新内存」的申请。
如果你的业务特点是「写多读少」,而且 OPS 非常高,那在 RDB 和 AOF rewrite 期间,就会产生大量的内存拷贝工作。
因为写请求很多,这会导致 Redis 父进程会申请非常多的内存。在这期间,修改 key 的范围越广,新内存的申请就越多。
如果你的机器内存资源不足,这就会导致 Redis 面临被 OOM 的风险!
要给 Redis 机器预留内存,其目的就是避免在 RDB 和 AOF rewrite 期间,防止 Redis OOM。
