持久化

Redis虽然是个内存数据库,但是Redis支持 RDB 和 AOF 两种持久化机制,将数据写往磁盘,可以有效地避免因进程退出造成的数据丢失的问题,当下次重启时利用之前持久化的文件即可实现数据恢复。

RDB

触发机制

RDB 持久化是把当前进程数据生成快照保存到硬盘的过程,触发 RDB 持久化过程分为手动触发自动触发
手动触发分别对应 save 和 bgsave 命令:

  • save 命令:阻塞当前 Redis 服务器(因为执行命令线程是单线程的),直到 RDB 过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用;
  • bgsave 命令:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短;

显然 bgsave 命令是针对 save 阻塞问题做的优化。因此 Redis 内部所有的涉及RDB 的操作都采用 bgsave 的方式。
除了执行命令手动触发之外,Redis 内部还存在自动触发 RDB 的持久化机制,例如以下场景:
1. 使用 save 相关配置,如: save m n (save 60 1000)。表示 m 秒内数据集存在 n 次修改时 (60秒内数据集有 1000 次修改),自动触发 bgsave;
2. 如果从节点执行全量复制操作,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点;
3. 执行 debug reload 命令重新加载 Redis 时,也会自动触发 save 操作;
4. 默认情况下执行 shutdown 命令时,如果没有开启 AOF 持久化功能则自动执行 bgsave;

bgsave执行流程

image.png
1. 执行 bgsave 命令,Redis 父进程判断当前是否存在正在执行的子进程,如:RDB/AOF 子进程,如果存在, bgsave 命令直接返回;
2. 父进程执行 fork 操作创建子进程,fork 操作过程中父进程会阻塞,通过 info stats 命令查看 latestfork_usec 选项,可以获取最近一个 fork 操作的耗时,单位为微秒;
3. 父进程 fork 完成后,bgsave 命令返回 Background saving started 信息并不再阻塞父进程,可以继续处理其它命令;
4. 子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换,执行 lastsave 命令可以获取最后一次生成 RDB 的时间,对 info 统计的 rdb_last_save_time 选项;
5. 进程发送信号给父进程表示完成,父进程更新统计信息,具体见 info Persistence 下的 rdb
* 相关选项;

RDB 文件

RDB 文件保存在 dir 配置指定的目录下,文件名通过 dbfilename 配置指定,可以通过执行 config set dir {newDir}config set dbfilename (newFileName} 运行期动态执行,当下次运行时 RDB 文件会保存到新目录。
Redis 默认采用 LZF 算法对生成的 RDB 文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数 config set rdbcompression { yes |no} 动态修改。
虽然压缩 RDB 会消耗 CPU,但可大幅降低文件的体积,方便保存到硬盘或通过网络发送给从节点,因此线上建议开启。
如果 Redis 加载损坏的 RDB 文件时拒绝启动,并打印如下日志: Short read or 0OM loading DB. Unrecoverable error,aborting now. 这时可以使用Redis 提供的 redis-check-dump 工具检测 RDB 文件并获取对应的错误报告。

RDB 的优缺点

RDB 的优点:RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照,非常适用于备份,全量复制等场景。比如每隔几小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中,用于灾难恢复。Redis 加载 RDB 恢复数据远远快于 AOF 的方式。
RDB 的缺点:RDB 方式数据没办法做到实时持久化/秒级持久化(会造成数据丢失)。因为 bgsave 每次运行都要执行 fork 操作创建子进程,属于重量级操作,频繁执行成本过高。RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个格式的 RDB 版本,存在老版本 Redis 服务无法兼容新版 RDB 格式的问题。针对 RDB 不适合实时持久化的问题,Redis 提供了 AOF 持久化方式来解决。

AOF

append only file 持久化,以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式。

使用

开启 AOF 功能需要设置配置:appendonly yes ,默认不开启。AOF 文件名通过 appendfilename 配置设置,默认文件名是 appendonly.aof。保存路径同 RDB 持久化方式一致,通过 dir 配置指定。

流程

AOF 的工作流程操作:命令写入( append)、文件同步( sync)、文件重写(rewrite)、重启加载( load)。
image.png
1. 所有的写入命令会追加到 aof_buf 缓冲区中;
2. AOF 缓冲区根据对应的策略向硬盘做同步操作;
3. 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的;
4. 当 Redis 服务器重启时,可以加载 AOF 文件进行数据恢复。

写入

AOF 命令写入的内容直接是 RESP 文本协议格式。例如:set hello world 这条命令,在 AOF 缓冲区会追加如下文本:

  1. *3
  2. $3
  3. set
  4. $5
  5. hello
  6. $5
  7. world

文本协议具有很好的兼容性。开启 AOF 后,所有写入命令都包含追加操作,直接采用文本协议格式,避免了二次处理开销。文本协议具有可读性,方便直接修改和处理。
Redis 使用单线程响应命令,如果每次写 AOF 文件命令都直接追加到硬盘,那么性能完全取决于当前硬 盘负载。先写入缓冲区 aof_buf 中,还有另一个好处,Redis 可以提供多种缓冲区同步硬盘的策略,在 性能和安全性方面做出平衡。
Redis 提供了多种 AOF 缓冲区同步文件策略,由参数 appendfsync 控制。

  • always:写人 aof_buf 后调用系统 fsync 操作同步到 AOF 文件,fsync 完成后线程返回命令 fsync 同步文件;
  • everysec:写人 aof_buf 后调用系统 write 操作,write 完成后线程返回。操作由专门线程每秒调用一次fsync 命令;
  • no:写入 aof_buf 后调用系统 write 操作,不对 AOF 文件做 fsync 同步,同步硬盘操作由操作系统负责,通常同步周期最长 30 秒;

    重写机制

    随着命令不断写入 AOF,文件会越来越大,为了解决这个问题,Redis引入 AOF 重写机制压缩文件体积。AOF文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程。
    重写后的 AOF 文件为什么可以变小有如下原因:
    1. 进程内已经超时的数据不再写入文件;
    2. 旧的 AOF 文件含有无效命令,如:set a 111、set a 222 等。重写使用进程内数据直接生成,这样新的AOF 文件只保留最终数据的写入命令;
    3. 多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush listc 可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset 等类型操作, 以 64 个元素为界拆分为多条。
    AOF 重写降低了文件占用空间,除此之外,另一个目的是:更小的 AOF 文件可以更快地被 Redis 加载。
    AOF 重写过程可以手动触发自动触发

  • 手动触发:直接调用 bgrewriteaof 命令。

  • 自动触发:根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定自动触发时机;
    • auto-aof-rewrite-min-size:表示运行 AOF 重写时文件最小体积,默认为 64MB;
    • auto-aof-rewrite-percentage:代表当前 AOF 文件空间和上一次重写后 AOF 文件空间的比值;

触发 AOF 重写流程
1. 执行 AOF 重写请求,如果当前进程正在执行 AOF 重写,请求不执行并返回如下响应 ERR Background append only file rewriting already in progress 。如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行,返回如下响应:Background append only file rewriting scheduled 。
2. 父进程执行 fork 创建子进程,开销等同于 bgsave 过程。
3. 主进程 fork 操作完成后,继续响应其他命令。
1. 所有修改命令依然写入 AOF 缓冲区并根据 appendfsync 策略同步到硬盘,保证原有 AOF 机制正确性。
2. 由于 fork 操作运用写时复制技术,子进程只能共享 fork 操作时的内存数据。由于父进程依然响应命令,Redis 使用 AOF 重写缓冲区保存这部分新数据,防止新 AOF 文件生成期间丢失这部分数据。
4. 子进程根据内存快照,按照命令合并规则写入到新的 AOF 文件。每次批量写入硬盘数据量由配置 aof-rewrite-incremental-fsync 控制,默认为 32MB,防止单次刷盘数据过多造成硬盘阻塞。
5. 新 AOF 文件写入完成后,子进程发送信号给父进程。
1. 父进程更新统计信息,具体见 info persistence 下的 aof_* 相关统计。
2. 父进程把 AOF 重写缓冲区的数据写入到新的 AOF 文件。
3. 使用新 AOF 文件替换老文件,完成 AOF 重写。

重启加载

AOF 和 RDB 文件都可以用于服务器重启时的数据恢复。redis 重启时加载 AOF 与 RDB 的顺序是怎么样的呢?
1. 当 AOF 和 RDB 文件同时存在时,优先加载 AOF;
2. 若关闭了 AOF,加载 RDB 文件;
3. 加载 AOF/RDB 成功,redis 重启成功;
4. AOF/RDB 存在错误,启动失败打印错误信息;
image.png

文件校验

加载损坏的 AOF 文件时会拒绝启动,对于错误格式的 AOF 文件,先进行备份,然后采用 redis-checkaof —fix 命令进行修复,对比数据的差异,找出丢失的数据,有些可以人工修改补全。
AOF 文件可能存在结尾不完整的情况,比如:机器突然掉电导致 AOF 尾部文件命令写入不全。Redis 提供了 aof-load-truncated 配置来兼容这种情况,默认开启。加载 AOF 时当遇到此问题时会忽略并继续启动,同时有警告日志。

混合持久化

重启 Redis 时,很少使用 RDB 来恢复内存状态,因为会丢失大量数据。通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项:混合持久化
通过配置可以开启混合持久化: aof‐use‐rdb‐preamble yes
如果开启了混合持久化,AOF 在重写时,不再是单纯将内存数据转换为 RESP 命令写入 AOF 文件,而是将重写这一刻之前的内存做 RDB 快照处理,并且将 RDB 快照内容和增量的 AOF 修改内存数据的命令存在一起,都写入新的 AOF 文件,新的文件一开始不叫 appendonly.aof,等到重写完新的 AOF 文件才会进行改名,覆盖原有的 AOF 文件,完成新旧两个 AOF 文件的替换。
于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。
混合持久化 AOF 文件结构如下:
image.png

主从架构

在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。Redis 也是如此,它提供了复制功能,实现了相同数据的多个 Redis 副本。

建立主从

参与复制的 Redis 实例划分为主节点 master 和从节点 slave。默认情况下,Redis 都是主节点。每个从 节点只能有一个主节点,而主节点可以同时具有多个从节点。复制的数据流是单向的,只能由主节点复 制到从节点。配置复制的方式有以下三种:
1. 在配置文件中加入 slaveof {masterHost } {masterPort} 随 Redis 启动生效;
2. 在 redis-server 启动命令后加入 —slaveof {masterHost} {masterPort } 生效;
3. 直接使用命令:slaveof {masterHost} { masterPort} 生效;
综上所述,slaveof 命令在使用时,可以运行期动态配置,也可以提前写到配置文件中
例如:本地启动两个端口为 6380 和 6381 的 Redis 节点。
在 6381 执行: slaveof 172.17.0.3 6380
image.png
slaveof 配置都是在从节点发起,这时 6380 作为主节点,6381 作为从节点。
复制关系建立后可以看到。
从节点 6381 上:
image.png
主节点 6380 上:
image.png slaveof 本身是异步命令,执行 slaveof 命令时,节点只保存主节点信息后返回,后续复制流程在节点内部异步执行。主从节点复制成功建立后,可以使用 info replication 命令查看复制相关状态。

断开主从

slaveof 命令不但可以建立复制,还可以在从节点执行 slaveof no one 来断开与主节点复制关系。例如:在 6381 节点上执行 slaveof no one 来断开复制。
断开复制主要流程:
1. 断开与主节点复制关系;
2. 从节点晋升为主节点;
从节点断开复制后并不会抛弃原有数据,只是无法再获取主节点上的数据变化。
image.png
通过 slaveof 命令还可以实现切主操作,所谓切主是指把当前从节点对主节点的复制切换到另一个主节点。执行 slaveof{ newMasterIp} { newMasterPort} 命令即可,例如:把 6381 节点从原来的复制 6380 节点变为复制63873 节点。
切主内部流程如下:
1. 断开与旧主节点复制关系;
2. 与新主节点建立复制关系;
3. 删除从节点当前所有数据;
4. 对新主节点进行复制操作;

只读

默认情况下,从节点使用 slave-read-only=yes 配置为只读模式。由于复制只能从主节点到从节点,对 于从节点的任何修改主节点都无法感知,修改从节点会造成主从数据不一致。

传输延迟

主从节点一般部署在不同机器上,复制时的网络延迟就成为需要考虑的问题,Redis 提供了repl-disable-tcp-nodelay 参数用于控制是否关闭 TCP_NODELAY,默认关闭。
当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如同机架或同机房部署。
当开启时,主节点会合并较小的 TCP 数据包从而节省带宽。默认发送时间间隔取决于 Linux 的内核,一般默认为 40 毫秒。这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署。

一主一从结构

一主一从结构是最简单的复制结构,用于主节点出现宕机时从节点提供故障转移支持。
image.png
当应用写命令并发量较高且需要持久化时,可以只在从节点上开启 AOF ,这样既保证数据安全性同时也避免了持久化对主节点的性能干扰。但需要注意的是,当主节点关闭持久化功能时,如果主节点脱机要避免自动重启操作。
因为主节点之前没有开启持久化功能自动重启后数据集为空,这时从节点如果继续复制主节点会导致从节点数据也被清空的情况,丧失了持久化的意义。安全的做法是在从节点上执行 slaveof no one 断开与主节点的复制关系,再重启主节点从而避免这一问题。

一主多从结构

image.png
对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。同时在日常开发中如果需要执行一些比较耗时的读命令,如:keys、sort 等,可以在其中一台从节点上执行,防止慢查询对主节点造成阻塞从而影响线上服务的稳定性。对于写并发量较高的场景,多个从节点会导致主节点写命令的多次发送从而过度消耗网络带宽,同时也加重了主节点的负载影响服务稳定性。

树状主从结构

树状主从结构使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。通过引入复制中间层,可以有效降低主节点负载和需要传送给从节点的数据量。
image.png
数据写入节点 A 后会同步到 B 和 C 节点,B 节点再把数据同步到 D 和 E 节点,数据实现了一层一层的向下复制。当主节点需要挂载多个从节点时为了避免对主节点的性能干扰,可以采用树状主从结构降低主节点压力。

复制原理

复制过程

在从节点执行 slaveof 命令后,复制过程便开始运作。

保存主节点信息,执行 slaveof 后从节点只保存主节点的地址信息便直接返回,这时建立复制流程还没有开始。

从节点内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。

从节点会建立一个 socket 套接字,专门用于接受主节点发送的复制命令。

从节点连接成功后打印日志。

如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行 slaveof no one 取消复制。

关于连接失败,可以在从节点执行 info replication 查看 master_link_down_since_seconds 指标,它会记录与主节点连接失败的系统时间。

从节点连接主节点失败时也会每秒打印日志。

连接建立成功后从节点发送 ping 请求进行首次通信,ping 请求主要目的:检测主从之间网络套接字是否可用、检测主节点当前是否可接受处理命令。从节点发送的 ping 命令成功返回,Redis 打印日志,并继续后续复制流程:Master replied to PING,replication can continue. . .

如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节 点相同的密码才能通过验证,如果验证失败复制将终止,从节点重新发起复制流程。

主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。Redis 在 2.8 版本以后采用新复制命令 psync 进行数据同步,原来的 sync 命令依然支持,保证新旧版本的兼容性。新版同步划分两种情况:全量同步部分同步

当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。

数据同步

Redis 在 2.8 及以上版本使用 psync 命令完成主从数据同步,同步过程分为:全量复制部分复制

  • 全量复制:一般用于初次复制场景,Redis 早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
  • 部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以 有效避免全量复制的过高开销。部分复制是对老版复制的重大优化,有效避免了不必要的全量复制操作。

全量复制流程图:
image.png
部分复制(断点续传)流程图:
image.png
当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。

问题一:InnoDB有多少种日志,作用是什么?

错误日志:记录出错信息,也记录一些警告信息或者正确的信息
查询日志:记录所有对数据库请求的信息,不论这些请求是否得到了正确的执行
慢查询日志:设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询的日志文件中
二进制日志:记录对数据库执行更改的所有操作
中继日志,事务日志。

问题二:Redis默认的持久化机制是什么?有什么优缺点?

默认是RDB
优点:
1、只有一个文件 dump.rdb,方便持久化。
2、容灾性好,一个文件可以保存到安全的磁盘。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。
缺点:
数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
可以结合AOF(Append-only file)持久化方式:这个持久化机制所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。

问题三:InnoDB和MyISAM的select count(*)哪个更快,为什么?

myisam更快,因为myisam内部维护了一个计数器,可以直接调取。

问题四:生产环境如何选择持久化机制RDB和AOF?

一般来说, 如果想达到足以媲美PostgreSQL的数据安全性,你应该同时使用两种持久化功能。在这种情况下,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。
有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快。
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。