Redis RDB 持久化遇到的问题
Table of content:
Reference 和推荐阅读
- 书籍「Redis 设计与实现」
- Redis 作者对 Redis 持久化和其他常见数据库持久化之间的异同 blog
- Redis 官方文档里面对 RDB 和 AOF 的介绍以及推荐的使用方式 https://redis.io/topics/persistence
- Background saving fails with a fork() error under Linux even if I have a lot of free RAM!
- Redis 内存使用优化和存储 http://www.infoq.com/cn/articles/tq-redis-memory-usage-optimization-storage
问题的背景
一个部署在测试环境的服务容器数量突然掉成了 0 ,整个服务挂了,容器日志是这样的异常
1 | MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error. |
上对应机器 sudo tail /var/log/redis/redis-server.log -n 20
看到的 Redis log
1 | 29348:M 31 Aug 00:24:35.060 # Can't save in background: fork: Cannot allocate memory |
看起来是 fork 进程时内存不足, 看到了 https://gist.github.com/kapkaev/4619127 提供的解决方案, 更改 Redis 的 stop-writes-on-bgsave-error
的值, 在 Redis example 配置文件里面是这么描述这个配置项的:
1 | By default Redis will stop accepting writes if RDB snapshots are enabled |
stop-writes-on-bgsave-error
默认为 yes
, 也就是如果 Redis 开启了 RDB 持久化并且最后一次失败了,Redis 默认会停止写入,让用户意识到数据的持久化没有正常工作。链接里面提供的解决方案是 conf set stop-writes-on-bgsave-error
设置为 no,只是让程序暂时忽略了这个问题,但是数据的持久化的问题并没有。但是 Redis 持久化相关的逻辑有哪些,这里持久化失败的原因是什么 ?
Redis 持久化
Redis 是内存数据库,它将数据库状态存储在内存里面,如果不想办法把存储在内存中的数据库状态保存到磁盘上,一旦服务器进程退出,服务器中的数据状态也会消失不见。为了解决这个问题,Redis 提供了集中持久化的方式:
- 定时快照方式(snapshot)
- 基于语句追加文件的方式(aof)
- 虚拟内存(vm)
- Diskstore 方式
前两种是数据都在内存中,即小数据量下磁盘落地功能。后两种是存储数据超过物理内存时的方式。VM 本来是作为 Redis 存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,但是其内存管理成本也非常的高, 目前已经处于被作者放弃的状态。而 diskstore 还在实验阶段。这本文主要提及前两种方式:
RDB 快照
RDB 的持久化既可以手动执行, 也可以根据服务器的配置定期自动执行。该功能可以将某个时间点上的数据库状态保存到一个 RDB 文件中。
RDB 文件是个经过压缩的二进制文件,通过该文件可以还原生成 RDB 文件时的数据库状态。因为 RDB 是保存在硬盘里面的,即使 Redis 进程退出,或者运行 Redis 的服务器停机,只要 RDB 文件依然存在,Redis 就可以用它来还原数据库状态。
有两个 Redis 命令可以用来生成 RDB 文件:
SAVA
SAVE 会阻塞 Redis 进程,直到 RDB 文件创建完成。在服务器进程阻塞期间,不能处理任何命令请求。
BGSAVE
BGSAVE 则会派生一个子进程,由子进程负责创建 RDB 文件,父进程继续处理命令请求。而执行 BGSAVE 期间,客户端发送的 BGSAVE 会被拒绝,因为同时执行两个 BGSAVE 会产生竞态条件。
AOF(Append Only File)
AOF 是通过保存所执行的写命令, 如 SET, SADD, RPUSH 来记录数据库状态。被写入 AOF 文件的所有命令都是以 Redis 的命令请求协议格式保存的,因为 Redis 的命令请求协议是纯文本格式。
AOF 持久化的实现分为:
- 命令追加(append)
- 文件写入
- 文件同步(sync)
当 AOF 持久化功能处于打开时,服务器在执行完一个写命令之后,会以协议的格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。Redis 有对应的进程来确定是否要将 aof_buf 的内容写入和保存到 AOF 文件里面。
两者怎么选择 ?
通常 AOF 文件的更新频率比 RDB 的更高,所以
- 如果服务器开启了 AOF,会优先使用 AOF 文件来还原数据库状态
- 只有在 AOF 持久化处于关闭状态时,服务器才会使用 RDB 文件来还原数据库状态
在 Redis 官方文档 里, 关于 AOF 和 RDB 的选择,提到了几点:
- 如果想要更好的数据安全保障,推荐两者都使用
- 如果可以忍受几分钟的数据丢失, 只用 RDB 是没有问题的
- 不太建议只开启 AOF 模式,因为 RDB snapshot 这样按照时间持久化的是个很不错的方式,同时可以让重启更快。
回到问题的解决方案
在当时 Redis 报内存不足时,对应机器上其实还剩余很多内存的。官方文档里面也提到这个话题 Background saving fails with a fork() error under Linux even if I have a lot of free RAM!
修改内核参数
1
echo 1 > /proc/sys/vm/overcommit_memory
Linux内核会根据参数 vm.overcommit_memory 参数的设置决定是否放行。
如果 vm.overcommit_memory = 1,直接放行
vm.overcommit_memory = 0:则比较此次请求分配的虚拟内存大小和系统当前空闲的物理内存加上 swap,决定是否放行。
vm.overcommit_memory = 2:则会比较进程所有已分配的虚拟内存加上此次请求分配的虚拟内存和系统当前的空闲物理内存加上 swap,决定是否放行。
更改回 Redis 配置
1
conf stop-writes-on-bgsave-error yes
关于头图
- 头图拍摄自迪拜 Mall, 天花板