Redis 是基于内存的数据库, 服务一旦宕机, 内存中的数据将全部丢失. 通常来说可以通过数据库来恢复这些数据, 但这会给数据库带来非常大的读压力, 并且这个过程会非常缓慢, 并导致程序响应慢, 因此 Redis 提供了把内存数据持久化到硬盘, 并通过备份文件来恢复数据的功能, 即持久化机制.
目前 Redis Documentation 上对持久化的支持有以下几种方案:
RDB 指对整个数据集在特定时间点生成快照 (point-to-time snapshot), 可用于Redis的数据备份, 转移和恢复. 它是 Redis 默认使用的持久化方案.
RDB 利用操作系统提供的写时复制 (Copy-on-Write) 机制来进行持久化, 即当主进程 P fork 出子进程时 Q 时, Q 和 P 共享同一块内存空间, 当 P 准备对某块内存进行写操作时, P 会将这块内存页进行复制, 并在新的副本上对数据进行修改, 而 Q 仍然读取原先的内存页. 这样既能够保证 Redis 实例继续服务外部流量, 又能够以最小的成本完成数据的持久化. 但正因如此, 持久化过程中的写操作是不会被记录的.
触发rdb持久化的方式有2种:
save
: 阻塞 Redis 进程, 并进行 RDB 持久化, 直到其完成为止, 对于内存占用大的实例会造成长时间阻塞.bgsave
: background save, 让 Redis 进程通过 fork 操作创建子进程, 并在子进程进行 RDB 持久化, 只在 fork 阶段阻塞 Redis 进程serverCron
, 默认每 100 ms 执行一次, 它的其中一项功能就是检查所有 save 命令的条件里是否有任意一条被满足. 如果不想使用自动触发, 把所有的 save 命令注释即可.save x y # 在 x 秒内如果至少有 y 个 key 值发生变化, 则触发RDB
save 60 900 # 在 60 秒内如果至少有 900 个 key 值发生变化, 则触发RDB
是否应该以尽可能高的频率来触发 RDB?
为了保证宕机时丢失的数据尽量少, 我们也许可以每分钟出发一次 RDB 进行数据备份. 虽然 bgsave 在子进程中执行, 不会阻塞主线程, 但仍然有一些问题:
从这两点出发可以认为触发 RDB 的频率并不是越高越好, 我们需要考虑 Redis 实例占用内存的大小以及全量数据写入硬盘的速度.
优点
缺点
AOF (Append Only File) 通过写日志的方式, 在 Redis 每次写操作完成后在日志里记录下此次执行的命令, 当服务器重启的时候通过顺序地重放这些日志来恢复数据.
配置
AOF 功能默认是关闭的, 需要通过修改 redis.conf 并重启 Redis 来开启.
# no by default
appendonly yes
appendfilename appendonly.aof
写后日志
和 MySQL 的写前日志 (Write-Ahead Logging) 不同, AOF 会在写操作完成后记录日志, 这样既能够保证 Redis 不阻塞并及时响应写操作, 还可以避免运行时检查出写操作命令不合法再回滚这条日志. 但如果在命令执行完之后, 写日志完成之前, 服务器发生了宕机, 也有可能会丢失数据.
工作流程
AOF的工作原理可以概括为几个步骤:命令追加(append)、文件写入与同步(fsync)、文件重写(rewrite)、重启加载(load).
当 AOF 持久化功能开启时, Redis 执行完一个写命令后, 会按照 RESP (Redis Serialization Protocol) 协议规定的格式把这条写命令追加到其维护的 AOF 缓冲区末尾.
AOF缓冲区 (aof_buf) 采用 Redis 特有的数据结构 SDS (Simple Dynamic String), 根据命令的类型, 使用不同的方法(catAppendOnlyGenericCommand
, catAppendOnlyExpireAtCommand
等), 来对命令进行处理, 最后写入缓冲区.
如果命令追加时正在进行 AOF 重写, 这些命令还会追加到重写缓冲区 aof_rewrite_buffer
.
由于硬盘的 I/O 性能较差, 文件读写速度远远比不上 CPU 的处理速度, 那么如果每次文件写入都等待数据写入硬盘, 会整体拉低操作系统的性能. 为了解决这个问题, 操作系统提供了延迟写(delayed write)机制来提高硬盘的I/O性能.
Redis 每次事件轮询结束前(beforeSleep
)都会调用函数 flushAppendOnlyFile
, 它会把 AOF 缓冲区中的数据写入内核缓冲区, 并且根据 appendfsync 的配置来决定采用何种策略把内核缓冲区中的数据写入磁盘, 即调用 fsync()
, 有三个可选项:
fsync()
, 安全性最高, 但性能最差fsync()
. 性能最好, 安全性最差.fsync()
. 这是官方推荐的策略, 也是默认配置, 能够兼顾性能和数据安全性, 只有在系统突然宕机的情况下会丢失 1 秒的数据.随着时间的增加, AOF 文件体积会越来越大, 导致磁盘占用空间更多, 数据恢复时间更长. 为了解决这个问题, Redis 引入了 AOF 重写 (AOF Rewrite) 机制, 通过创建新的 AOF 文件, 将旧文件中的多条命令整合成为新文件中的单条命令, 并替换旧文件, 来减少 AOF 文件的体积.
重写在何时发生?
和 RDB 的触发方式类似, AOF重写可以通过手动或自动触发.
bgrewriteaof
命令, 如果当前不存在正在执行的 bgsave 或 bgrewriteaof 子进程, 那么重写会立即执行, 否则会等待子进程操作结束后再执行.auto-aof-rewrite-percentage
: 当前AOF文件(aof_current_size)和上一次重写发生后AOF文件大小(aof_base_size)相比, 其增加的比例, 默认为100, 即当 aof_current_size == 2 * aof_base_size 时触发auto-aof-rewrite-min-size
: 运行BGREWRITEAOF
时AOF文件占用空间最小值, 默认为64MB重写的流程是怎么样的?
整个过程可以参考下图:
Redis启动时把aof_base_size
初始化为当时aof文件的大小, Redis运行过程中, 当AOF文件重写操作完成时, 会对其进行更新;aof_current_size
为serverCron
执行时AOF文件的实时大小. 当满足以下两个条件时, AOF文件重写就会触发:
AOF重写会阻塞吗?
AOF 的重写过程是由后台进程 bgrewriteaof 来完成的. 主线程 fork 出后台的 bgrewriteaof 子进程, fork 操作会把主线程的内存拷贝一份给 bgrewriteaof 子进程, 这里面就包含了数据库的最新数据. 然后, bgrewriteaof 子进程逐一把拷贝的数据写成操作, 并记入重写日志, 因此在重写过程中, 只有当 fork 操作发生时会阻塞主线程.
Redis启动后通过loadDataFromDisk
函数执行数据加载, 流程大致如下:
AOF能保证数据完整性么?
如果在对AOF文件进行写操作时发生了宕机, 或磁盘满了, 由于延迟写的特点, AOF的RESP命令可能会因为被截断而不完整. 发生这种情况时, Redis会按照配置项aof-load-truncated
的值来进行不同的操作:
优点
redis-check-aof
工具修复缺点