标签 持久化 下的文章

Redis 持久化原理

概述


Redis 是个内存数据库, 数据全部存储在内存中, 如果突然宕机, 数据就会全部丢失, 因此有了将数据刷到硬盘保存的持久化机制. Redis 持久化分两种方式, 一种是 RDB 快照, 另一种是 AOF 日志.

快照就是全量备份, 内存数据的二进制序列化形式, 在存储上非常紧凑. AOF 日志是连续的增量备份, 记录的是内存数据修改的指令记录文本.

快照


由于 Redis 是单线程程序, 还要同时负责多个客户端的并发读写操作和内存数据结构的逻辑读写. 所以快照持久化面临如下两个问题:

  • 快照需要大量 IO 操作可能会阻塞线上业务, 拖慢整体性能, 而且快照又不能使用多路复用
  • 持久化的同时, 还需要接受客户端请求, 正在持久的数据可能会被修改删除

Redis 使用 操作系统的多进程 COW ( Copy on Write ) 机制来实现快照持久化

原理

Redis 在持久化时会调用 glibc 的函数 fork 产生一个子进程. 子进程做数据持久化, 不会修改现有的内存数据结构, 它只是对现有的数据结构遍历读取, 然后序列化写到磁盘中. 但父进程不一样, 它必须持续服务客户端请求, 然后对内存数据结构进行不间断修改.

这是就会使用到操作系统的 COW 机制来进行数据段页面的分离.

当父进程对一个页面的数据进行修改时, 会将被共享的页面复制一份分离出来, 然后对这个新复制的页面进行修改, 子进程继续复制原有页面, 不会受到影响, 也就是子进程被创建那一刻的数据, 所以为什么叫"快照".

随着父进程的持续修改, 越来越多的共享页面被复制分离出来, 内存就会持续增长, 不用担心内存会暴增, 因为不会超过原有数据内存的2倍. 而且往往冷数据占比较多, 很少会出现所有的页面都被复制分离的情况.

扩展一下, 因为快照要遍历整个内存, 同时大块写磁盘也是非常耗时的IO操作, 所以建议在从节点上进行. 由于从节点可能会因为网络等问题, 造成同步延迟, 就会操作快照不全, 所以监控也很重要.

AOF


aof 日志存储的是 Redis 服务器的顺序指令序列, aof 日志只记录对内存进行修改的记录.

aof 同样也面临着两个问题:

  • 随着实例的运行, 修改性指令序列越来越多, 在存储和重放(重启后恢复)上都存在着性能问题
  • aof 写日志文件的频率, 过快会因为IO拉升机器负载, 过慢如果机器突然宕机就会丢失数据

重写

为解决第一个问题, Redis 提供了 bgrewriteaof 指令用于对 aof 日志瘦身. 其原理是开启一个子进程对现有内存数据进行遍历, 转换成一些列 Redis 操作指令, 序列化到一个新的 aof 日志文件中. 序列化完毕后的再将操作期间发生的增量 aof 日志追加到新的日志中, 然后代替旧的日志文件.

fsync

进程对 aof 日志文件写操作时, 实际是将内容写到操作系统内核为文件描述符分配的一个内存缓存中, 然后内核 异步 将数据刷到磁盘.

为解决第二个问题, Redis 提供配置刷新周期, 通常是 1s 就会调用 Linux 的 glibc 提供的 fsync(int fd)函数, 它可以将指定文件的内容强制从内核缓存刷到磁盘.

扩展一下. Redis 还提供了另外两种可选方案, 生产环境中基本不会使用, 了解即可

  • 一是永不调用 fsync, 让操作系统来决定合适刷到磁盘
  • 另一个是每来一条指令就调一次 fsync

混合持久化


Redis 4.0 提供

快照和aof日志都有各自的痛点

  • 快照因为是每隔一段时间持久化一次, 就会丢失宕机时刻与上一次持久化之间的数据
  • aof 因为存储的是指令序列, 恢复重放时要花费很长时间

综合利弊, 使用 aof 还是更靠谱一点, 现在 Redis 4.0 提供了更好的混合持久化选项

混合持久化

  • 将 rdb 文件的内容和增量的 aof 日志放在一起
  • aof 日志只存储 rdb 持久化开始到当前发生的增量日志
  • 重启时, 先加载 rbd 内容, 再重放增量 aof 日志

这样就可以解决上面的两个痛点