目录
持久化
主从复制
哨兵
Cluster集群
RDB 持久化
手动触发
自动触发
RDB 执行流程
RDB 载入
AOF 持久化
执行流程
命令追加
文件写入和文件同步
appendfsync always
appendfsync no
appendfsync everysecond
文件重写
文件重写流程
载入
对比
nginx、tomcat、mysql等服务都具有预防单点故障、提高整体性能和安全性的功能,当然,Redis也不例外
在Redis中,高可用通过一系列技术和配置,如主从复制、快速容灾等,确保在发生故障或异常情况时,系统能够继续提供服务,避免单点故障,并最大限度地减少服务中断时间,除此之外还需考虑数据容量的扩展、数据安全不会丢失等
实现高可用的方式有很多,持久化、主从复制、哨兵、Cluster
持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失
主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制
在主从复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制
通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案
先从持久化开始
持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免服务器断电等原因导致Redis进程异常退出后数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。
Redis 有两种方式进行持久化,RDB 持久化和 AOF 持久化
RDB 持久化:原理是将 Reids在内存中的数据库记录定时保存到磁盘上。
AOF 持久化:原理是将 Reids 的操作日志以追加的方式写入文件,类似于MySQL的binlog 原理是将 Reids 的操作日志以追加的方式写入文件,类似于MySQL的binlog
由于AOF持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此AOF是目前主流的持久化方式,不过RDB持久化仍然有其用武之地。
RDB持久化是指在指定的时间间隔内将内存中当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),用二进制压缩存储,保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。
RDB 持久化的触发机制分为手动触发和自动触发
save 命令和 bgsave 命令都可以生成RDB文件
save 命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止。在Redis服务器阻塞期间,服务器不能处理任何命令请求,在数据量较大时,save 命令可能会占用较长时间,影响系统性能。
bgsave 命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求,命令执行过程中,只有fork子进程时会阻塞服务器
在自动触发RDB持久化时,Redis也会选择bgsave而不是save来进行持久化。
自动触发通常是在配置文件中,通过 save m n ,指定在m 秒内 发生 n 次变化时,会触发bgsave
vim /etc/redis/6379.conf
---大约在219 行的位置,可以找到 RDB 自动触发的配置
save 900 1 :当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave
save 300 10 :当时间到300秒时,如果redis数据发生了至少10次变化,则执行bgsave
save 60 10000 :当时间到60秒时,如果redis数据发生了至少10000次变化,则执行bgsave
--254行--指定RDB文件名
dbfilename dump.rdb
--264行--指定RDB文件和AOF文件所在目录
dir /var/lib/redis/6379
--242行--是否开启RDB文件压缩
rdbcompression yes
保存退出
重启redis
除了自动触发,还有一些特殊情况会触发bgsave
比如,在主从复制场景下,如果从节点执行全量复制,则主节点会执行 bgsave 命令,并将 RDB 文件发送给从节点
或者 执行shutdown 命令时,自动执行RDB持久化
首先 Redis 父进程首先判断当前是否在执行 save或 bgsave 或者 bgrewriteaof 的子进程,如果在执行,bgsave 命令会直接返回,不会创建新的子进程。bgsave/bgrewriteaof 的两个并发的子进程同时执行大量的磁盘写操作,可能会引起严重的性能问题,如磁盘I/O竞争、内存占用过高等。
若没有在执行,则父进程执行 fork 操作创建子进程,这个过程中父进程是阻塞的,Redis 不能响应来自客户端的任何命令
父进程执行完fork 之后,bgsave命令返回”Background saving started”信息,表示RDB文件的创建已经在后台开始。此时,父进程不再阻塞,并可以继续响应其他客户端命令
子进程遍历父进程的内存,生成当前数据集的快照,将生成的快照数据写入一个临时RDB文件,RDB文件创建完成后,子进程会使用临时文件对原有的RDB文件进行原子替换,确保数据一致性
完成创建后会发送信号给父进程,表示完成,父进程更新统计信息
RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败
RDB持久化是将进程数据写入文件,而AOF持久化则是将Redis执行的每次写、删除命令记录到单独的日志文件中,查询操作不会记录;当Redis重启时再次执行AOF文件中的命令来恢复数据。
与RDB 相比,AOF 的实时性更好,因此已成为主流的持久化方案
开启 AOF
Redis 服务器默认开启 RDB ,关闭AOF;要开启 AOF ,需要在配置文件中配置
vim /etc/redis/6379.conf
--在700行的位置
appendonly yes
--704行--指定AOF文件名称
appendfilename "appendonly.aof"
--796行--是否忽略最后一条可能存在问题的指令
aof-load-truncated yes
保存退出,重启redis
由于需要记录Redis的每条写命令,因此AOF不需要触发,下面介绍AOF的执行流程
AOF 的执行流程包括
命令追加就是将Redis 的写命令追加到缓冲区 aof_buf中
Redis 不是将命令直接写入文件,而是先放入缓冲区,为了避免每次有写命令都写入硬盘,导致硬盘IO成为Redis负载的瓶颈
命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点。在AOF文件中,除了用于指定数据库的select命令(如select 0为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。
为了提高文件写入效率,在现代操作系统中,当用户调用write
函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync
、fdatasync
等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。
AOF 缓存区的同步文件策略存在三种同步方式
命令写入aof_buf
后立即调用系统fsync
操作同步到AOF文件,fsync
完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
当appendfsync 设置为 no 时,Redis 会在每次将命令写入到 aof_buf 缓冲区后,立即调用系统的write 操作,将数据写入磁盘的缓冲区。然而,Redis 并不会在每次写入后立即进行 fsync 同步操作,而是将文件同步的任务交给操作系统处理。
通常,操作系统会每隔 30 秒左右自动执行一次文件同步,将数据从磁盘的缓冲区真正写入到物理磁盘中
这种情况下,文件同步时间不可控,容易造成缓冲区数据堆积过多,从而无法保证数据的安全性
这种方式其实在 appendfsync always 和appendfsync no 之间做了一种中和,每当 Redis 接收到一条写入命令时,它会将该命令先写入到 aof_buf 缓冲区中,之后立即调用系统的 write 操作,将缓冲区的数据写入到磁盘的缓存中,但此时并不执行 fsync 操作(不会立即将数据同步到磁盘)。此外 ,Redis 启动了一个专门的后台线程,每秒钟执行一次sync 操作,将磁盘缓存中的数据真正同步到物理磁盘上
因此这也是 Redis 的默认配置
随着使用时间变长,Redis 服务器执行的命令会越来越多,AOF 文件也会越堆越大,如果不加以处理,最终会导致影响服务器正常运行,而且数据恢复的时间也会变得很长
所以,就需要对AOF 文件进行定期重写,以减小 AOF 文件的大小,重写会把Redis 进程内的数据转化为写命令,同步到新的 AOF 文件中,不会对旧的 AOF 文件进行任何读取或者写入操作
文件重写操作作为一个优化来说,就像车一样,不是必需品,但有能力还是建议购入的;
那么为什么将进程内数据转化成命令,所占空间就减少了?
因为过期的数据不会再写入文件
无效的命令也不再写入文件,比如有些数据已经被删除了,或者被重复设值
多条命令也可以合并成一条命令
文件重写也分为自动触发和手动触发
手动触发:直接调用 bgrewriteaof 命令,该命令的执行与 bgsave 类似,都是通过 fork 子进程来进行具体的工作,并且仅在 fork 时会出现阻塞。
自动触发:通过配置文件中的两个选项来控制的:auto-aof-rewrite-min-size
和 auto-aof-rewrite-percentage
。Redis 会根据这两个选项来判断何时自动执行 BGREWRITEAOF
,以优化 AOF 文件的大小和写入性能。
auto-aof-rewrite-min-size
:当 AOF 文件的大小达到该值时,才会考虑触发自动重写。也就是说,文件小于该值时,即便满足其他条件,也不会触发重写。
auto-aof-rewrite-percentage
:表示当前 AOF 文件的大小与上一次重写后的大小的增长百分比。例如,如果设置为 100
,那么当 AOF 文件的大小比上次重写时大了 100%(即翻倍)时,满足触发条件。
当两个条件都满足时,触发AOF 重写
vim /etc/redis/6379.conf
--729 行左右
auto-aof-rewrite-percentage xxxx
auto-aof-rewrite-min-size xxxx
AOF 文件重写的流程如下:Redis 父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof 的子进程,如果存在则 bgrewriteaof 命令直接返回,如果存在 bgsave 命令则等 bgsave 执行完成后再执行。父进程执行 fork 操作创建子进程,这个过程中父进程是阻塞的。父进程 fork 后,bgrewriteaof 命令返回 "Background append only file rewrite started" 信息并不再阻塞父进程,可以继续响应其他命令。此时,Redis 的所有写命令依然写入 AOF 缓冲区,并根据 appendfsync 策略同步到硬盘,保证原有 AOF 机制的正常运行。由于 fork 操作使用写时复制技术,子进程只能共享 fork 操作时的内存数据,而父进程依然在响应命令,因此 Redis 使用 AOF 重写缓冲区(aof_rewrite_buf)保存这部分数据,以防止在新 AOF 文件生成期间丢失这部分数据。也就是说,bgrewriteaof 执行期间,Redis 的写命令同时追加到 aof_buf 和 aof_rewrite_buf 两个缓冲区。子进程根据内存快照,按照命令合并规则将数据写入到新的 AOF 文件中。当子进程写完新的 AOF 文件后,会向父进程发信号,父进程更新统计信息,可以通过 info persistence
查看。随后,父进程会将 AOF 重写缓冲区中的数据写入到新的 AOF 文件中,确保新 AOF 文件与当前服务器的数据库状态保持一致,最后使用新的 AOF 文件替换旧文件,完成 AOF 重写。
当 AOF 开启时,Redis 启动时会优先加载 AOF 文件来恢复数据,只有在 AOF 关闭时,才会加载 RDB 文件进行恢复。如果 AOF 开启但 AOF 文件不存在,即使有 RDB 文件,Redis 也不会加载。当 Redis 加载 AOF 文件时,会对其进行校验,如果文件损坏,日志中会打印错误信息并导致 Redis 启动失败。但如果 AOF 文件尾部不完整(例如由于机器突然宕机),且 aof-load-truncated
参数开启,日志中会输出警告,Redis 会忽略 AOF 文件的尾部并启动成功。aof-load-truncated
参数默认是开启的。
RDB 持久化
RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多,但RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而且RDB 文件需要满足特定的格式,兼容性差。
对于 RDB 持久化,一方面时 bgsave 在进行 fork 操作时 Redis 主进程会阻塞,另一方面,子进程向硬盘写数据也会带来 IO 压力
AOF 持久化
AOF 支持支持秒级持久化、兼容性好,但文件太大、恢复速度较慢,对性能的影响更大
对于AOF持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO压力更大,甚至可能造成AOF追加阻塞问题。