在这篇文章,我们一起了解 Redis 使用中非常重要的两个机制:Reids 持久化和主从复制。
什么是 Redis 持久化?
Redis 作为一个键值对内存数据库(NoSQL),数据都存储在内存当中,在处理客户端请求时,所有操作都在内存当中进行,如下所示
这样做有什么问题呢?其实,只要稍微有点计算机基础知识的人都知道,存储在内存当中的数据,只要服务器关机(各种原因引起的),内存中的数据就会消失了。
不仅服务器关机会造成数据消失,Redis 服务器守护进程退出,内存中的数据也一样会消失。
对于只把 Redis 当缓存来用的项目来说,数据消失或许问题不大,重新从数据源把数据加载进来就可以了。
但如果直接把用户提交的业务数据存储在 Redis 当中,把 Redis 作为数据库来使用,在其放存储重要业务数据,那么 Redis 的内存数据丢失所造成的影响也许是毁灭性。
为了避免内存中数据丢失,Redis 提供了对持久化的支持,我们可以选择不同的方式将数据从内存中保存到硬盘当中,使数据可以持久化保存。
Redis 提供了 RDB 和 AOF 两种不同的数据持久化方式,下面我们就来详细介绍一下这种不同的持久化方式吧。
RDB
RDB 是一种快照存储持久化方式,具体就是将 Redis 某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为 dump.rdb,而在 Redis 服务器启动时,会重新加载 dump.rdb 文件的数据到内存当中恢复数据。
①开启 RDB 持久化方式
开启 RDB 持久化方式很简单,客户端可以通过向 Redis 服务器发送 Save 或 Bgsave 命令让服务器生成 RDB 文件,或者通过服务器配置文件指定触发 RDB 条件。
save 命令:是一个同步操作。
# 同步数据到磁盘上
> save
当客户端向服务器发送 Save 命令请求进行持久化时,服务器会阻塞 Save 命令之后的其他客户端的请求,直到数据同步完成。
如果数据量太大,同步数据会执行很久,而这期间 Redis 服务器也无法接收其他请求,所以,最好不要在生产环境使用 Save 命令。
Bgsave:与 Save 命令不同,Bgsave 命令是一个异步操作。
# 异步保存数据集到磁盘上
> bgsave
当客户端发服务发出 Bgsave 命令时,Redis 服务器主进程会 Forks 一个子进程来数据同步问题,在将数据保存到 RDB 文件之后,子进程会退出。
所以,与 Save 命令相比,Redis 服务器在处理 Bgsave 采用子线程进行 IO 写入。
而主进程仍然可以接收其他请求,但 Forks 子进程是同步的,所以 Forks 子进程时,一样不能接收其他请求。
这意味着,如果 Forks 一个子进程花费的时间太久(一般是很快的),Bgsave 命令仍然有阻塞其他客户的请求的情况发生。
服务器配置自动触发:除了通过客户端发送命令外,还有一种方式,就是在 Redis 配置文件中的 Save 指定到达触发 RDB 持久化的条件,比如【多少秒内至少达到多少写操作】就开启 RDB 数据同步。
例如我们可以在配置文件 redis.conf 指定如下的选项:
# 900s内至少达到一条写命令
save 900 1
# 300s内至少达至10条写命令
save 300 10
# 60s内至少达到10000条写命令
save 60 10000
之后在启动服务器时加载配置文件。
# 启动服务器加载配置文件
redis-server redis.conf
这种通过服务器配置文件触发 RDB 的方式,与 Bgsave 命令类似,达到触发条件时,会 Forks 一个子进程进行数据同步。
不过最好不要通过这方式来触发 RDB 持久化,因为设置触发的时间太短,则容易频繁写入 RDB 文件,影响服务器性能,时间设置太长则会造成数据丢失。
②RDB 文件
前面介绍了三种让服务器生成 RDB 文件的方式,无论是由主进程生成还是子进程来生成,其过程如下:
生成临时 RDB 文件,并写入数据。
完成数据写入,用临时文代替代正式 RDB 文件。
删除原来的 DB 文件。
RDB 默认生成的文件名为 dump.rdb,当然,我可以通过配置文件进行更加详细配置。
比如在单机下启动多个 Redis 服务器进程时,可以通过端口号配置不同的 RDB 名称,如下所示:
# 是否压缩rdb文件
rdbcompression yes # rdb文件的名称
dbfilename redis-6379.rdb # rdb文件保存目录
dir ~/redis/
RDB的几个优点:
与 AOF 方式相比,通过 RDB 文件恢复数据比较快。
RDB 文件非常紧凑,适合于数据备份。
通过 RDB 进行数据备份,由于使用子进程生成,所以对 Redis 服务器性能影响较小。
RDB 的几个缺点:
如果服务器宕机的话,采用 RDB 的方式会造成某个时段内数据的丢失,比如我们设置 10 分钟同步一次或 5 分钟达到 1000 次写入就同步一次,那么如果还没达到触发条件服务器就死机了,那么这个时间段的数据会丢失。
使用 Save 命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
使用 Bgsave 命令在 Forks 子进程时,如果数据量太大,Forks 的过程也会发生阻塞,另外,Forks 子进程会耗费内存。
AOF
聊完了 RDB,来聊聊 Redis 的另外一个持久化方式:AOF(Append-only file)。
与 RDB 存储某个时刻的快照不同,AOF 持久化方式会记录客户端对服务器的每一次写操作命令,并将这些写操作以 Redis 协议追加保存到以后缀为 AOF 文件末尾。
在 Redis 服务器重启时,会加载并运行 AOF 文件的命令,以达到恢复数据的目的。
①开启 AOF 持久化方式
Redis 默认不开启 AOF 持久化方式,我们可以在配置文件中开启并进行更加详细的配置,如下面的 redis.conf 文件:
# 开启aof机制
appendonly yes # aof文件名
appendfilename "appendonly.aof" # 写入策略,always表示每个写操作都保存到aof文件中,也可以是everysec或no
appendfsync always # 默认不重写aof文件
no-appendfsync-on-rewrite no # 保存目录
dir ~/redis/
②三种写入策略
在上面的配置文件中,我们可以通过 appendfsync 选项指定写入策略,有三个选项:
appendfsync always
# appendfsync everysec
# appendfsync no
always:客户端的每一个写操作都保存到 AOF 文件当中,这种策略很安全,但是每个写操作都有 IO 操作,所以也很慢。
everysec:appendfsync 的默认写入策略,每秒写入一次 AOF 文件,因此,最多可能会丢失 1s 的数据。
no:Redis 服务器不负责写入 AOF,而是交由操作系统来处理什么时候写入 AOF 文件。更快,但也是最不安全的选择,不推荐使用。
③AOF 文件重写
AOF 将客户端的每一个写操作都追加到 AOF 文件末尾,比如对一个 Key 多次执行 Incr 命令,这时候,AOF 保存每一次命令到 AOF 文件中,AOF 文件会变得非常大。
incr num 1
incr num 2
incr num 3
incr num 4
incr num 5
incr num 6
...
incr num 100000
AOF 文件太大,加载 AOF 文件恢复数据时,就会非常慢,为了解决这个问题,Redis 支持 AOF 文件重写。
通过重写 AOF,可以生成一个恢复当前数据的最少命令集,比如上面的例子中那么多条命令,可以重写为:
set num 100000
AOF 文件是一个二进制文件,并不是像上面的例子一样,直接保存每个命令,而使用 Redis 自己的格式,上面只是方便演示。
两种重写方式:通过在 redis.conf 配置文件中的选项 no-appendfsync-on-rewrite 可以设置是否开启重写。
这种方式会在每次 Fsync 时都重写,影响服务器性能,因此默认值为 no,不推荐使用。
# 默认不重写aof文件
no-appendfsync-on-rewrite no
客户端向服务器发送 bgrewriteaof 命令,也可以让服务器进行 AOF 重写。
# 让服务器异步重写追加aof文件命令
> bgrewriteaof
AOF 重写方式也是异步操作,即如果要写入 AOF 文件,则 Redis 主进程会 Forks 一个子进程来处理,如下所示:
重写 AOF 文件的好处:
压缩 AOF 文件,减少磁盘占用量。
将 AOF 的命令压缩为最小命令集,加快了数据恢复的速度。
③AOF 文件损坏
在写入 AOF 日志文件时,如果 Redis 服务器宕机,则 AOF 日志文件文件会出格式错误。
在重启 Redis 服务器时,Redis 服务器会拒绝载入这个 AOF 文件,可以通过以下步骤修复 AOF 并恢复数据:
备份现在 AOF 文件,以防万一。
- 使用 redis-check-aof 命令修复 AOF 文件,该命令格式如下:
# 修复aof日志文件
$ redis-check-aof -fix file.aof
重启 Redis 服务器,加载已经修复的 AOF 文件,恢复数据。
AOF 的优点:
AOF 只是追加日志文件,因此对服务器性能影响较小,速度比 RDB 要快,消耗的内存较少。
AOF 的缺点:
AOF 方式生成的日志文件太大,即使通过 AFO 重写,文件体积仍然很大。
恢复数据的速度比 RDB 慢。
选择 RDB 还是 AOF 呢?
通过上面的介绍,我们了解了 RDB 与 AOF 各自的优点与缺点,到底要如何选择呢?
通过下面的表示,我们可以从几个方面对比一下 RDB 与 AOF,在应用时,要根据自己的实际需求,选择 RDB 或者 AOF。
其实,如果想要数据足够安全,可以两种方式都开启,但两种持久化方式同时进行 IO 操作,会严重影响服务器性能,因此有时候不得不做出选择。
当 RDB 与 AOF 两种方式都开启时,Redis 会优先使用 AOF 日志来恢复数据,因为 AOF 保存的文件比 RDB 文件更完整。
小结:上面讲了一大堆 Redis 的持久化机制的知识,其实,如果你只是单纯把 Redis 作为缓存服务器,那么可以完全不用考虑持久化。
但是,在如今的大多数服务器架构中,Redis 不单单只是扮演一个缓存服务器的角色,还可以作为数据库,保存我们的业务数据,此时,我们则需要好好了解有关 Redis 持久化策略的区别与选择。
什么是 Reids 主从复制?
上面,我们了解了 Redis 两种不同的持久化方式,Redis 服务器通过持久化,把 Redis 内存中持久化到硬盘当中,当 Redis 宕机时,我们重启 Redis 服务器时,可以由 RDB 文件或 AOF 文件恢复内存中的数据。
不过持久化后的数据仍然只在一台机器上,因此当硬件发生故障时,比如主板或 CPU 坏了,这时候无法重启服务器,有什么办法可以保证服务器发生故障时数据的安全性?或者可以快速恢复数据呢?
想做到这一点,我们需要再了解 Redis 另外一种机制:主从复制。
Redis 的主从复制机制是指可以让从服务器(Slave)能精确复制主服务器(Master)的数据,如下图所示:
上面的图表示的是一台 Master 服务器与 Slave 服务器的情况,其实一台 Master 服务器也可以对应多台 Slave 服务器,如下图所示:
另外,Slave 服务器也可以有自己的 Slave 服务器,这样的服务器称为 Sub-Slave。
而这些 Sub-Slave 通过主从复制最终数据也能与 Master 保持一致,如下图所示:
主从复制的方式和工作原理
Redis 的主从复制是异步复制,异步分为两个方面:
一个是 Master 服务器在将数据同步到 Slave 时是异步的,因此 Master 服务器在这里仍然可以接收其他请求。
一个是 Slave 在接收同步数据也是异步的。
①复制方式
Redis 主从复制分为以下三种方式:
当 Master 服务器与 Slave 服务器正常连接时,Master 服务器会发送数据命令流给 Slave 服务器,将自身数据的改变复制到 Slave 服务器。
当因为各种原因 Master 服务器与 Slave 服务器断开后,Slave 服务器在重新连上 Master 服务器时会尝试重新获取断开后未同步的数据即部分同步,或者称为部分复制。
如果无法部分同步(比如初次同步),则会请求进行全量同步,这时 Master 服务器会将自己的 RDB 文件发送给 Slave 服务器进行数据同步,并记录同步期间的其他写入,再发送给 Slave 服务器,以达到完全同步的目的,这种方式称为全量复制。
②工作原理
Master 服务器会记录一个 Replication Id 的伪随机字符串,用于标识当前的数据集版本,还会记录一个当数据集的偏移量 Offset。
不管 Master 是否有配置 Slave 服务器,Replication Id 和 Offset 会一直记录并成对存在,我们可以通过以下命令查看 Replication Id和 Offset:
> info repliaction
通过 redis-cli 在 Master 或 Slave 服务器执行该命令会打印类似以下信息(不同服务器数据不同,打印信息不同):
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=9472,lag=1
master_replid:2cbd65f847c0acd608c69f93010dcaa6dd551cee
master_repl_offset:9472
当 Master 与 Slave 正常连接时,Slave 使用 PSYNC 命令向 Master 发送自己记录的旧 Master 的 Replication id 和 Offset。
而 Master 会计算与 Slave 之间的数据偏移量,并将缓冲区中的偏移数量同步到 Slave,此时 Master 和 Slave 的数据一致。
而如果 Slave 引用的 Replication 太旧了,Master 与 Slave 之间的数据差异太大,则 Master 与 Slave 之间会使用全量复制的进行数据同步。
配置主从复制
Redis 的主从配置非常简单,我们可以使用两种方式来配置主从服务器,在这时我们先假设 Redis 的 Master 服务器地址为 192.168.0.101。
客户端发送同步命令:
# 向客户端
saveof 192.168.1.101 6379
Slave 服务器配置主服务器:在这里 Slave 服务器的 redis.conf 通过 saveof 选项,可以指定 Master 服务器,如下:
slaveof 192.168.1.101 6379
通过上面两种方式的配置,Master 服务器与 Slave 服务器便已经可以开始进行数据同步了。
Master 要求验证:上面配置的是 Master 服务器没有设置密码的情况,如果 Master 设置了密码,则可以在连接到 Slave 服务器的 redis-cli 执行下面的命令:
# <password>指代实际的密码
config set masterauth <password>
或者在 Slave 服务器的 redis.conf 中配置下面的选项:
# <password>指代实际的密码
masterauth <password>
避免 Slave 被清空
Slave 会被清空?Slave 不用同步了 Master 的数据吗?备份的数据怎么会清空了呢?
当 Master 服务器关闭了持久化时,如果发生故障后自动重启时,由本地没有保存持久化的数据,重启的 Redis 内存数据为空,而 Slave 会自动同步 Master 的数据,这时候,Slave 服务器的数据也会被清空。
如何避免 Slave 被清空呢?如果条件允许(一般都可以的),Master 服务器还是要开启持久化,这样 Master 故障重启时,可以快速恢复数据,而同步这台 Master 的 Slave 数据也不会被清空。
如果 Master 不能开启持久化,则不应该设置让 Master 发生故障后重启(有些机器会配置自动重启),而是将某个 Slave 服务器升级为 Master 服务器,对外继续提供服务。
Slave 默认为只读的
在 Redis 2.6 以后,Slave 只读模式是默认开启的,我们可以通过配置文件中的 slave-read-only 选项配置是否开启只读模式:
# 默认是yes
slave-read-only yes/no
或者在客户端中通过 config set 命令设置是否开启只读模式:
config set slave-read-only no
上面将 Slave 服务器设置为可以写入,但是要注意,如果 Slave 也配置了自己的从服务器(Sub-Slave),那么 Sub-Slave 只会同步从 Master 服务器同步到 Slave 的数据,而并会同步我们直接写入 Slave 服务器的数据。
主从复制中的 Key 过期问题
我们都知道 Redis 可以通过设置 Key 的过期时间来限制 Key 的生存时间,Redis 处理 Key 过期有惰性删除和定期删除两种机制。
而在配置主从复制后,Slave 服务器就没有权限处理过期的 Key,这样的话,对于在 Master 上过期的 Key,在 Slave 服务器就可能被读取。
所以 Master 会累积过期的 Key,积累一定的量之后,发送 Del 命令到 Slave,删除 Slave 上的 Key。
如果 Slave 服务器升级为 Master 服务器 ,则它将开始独立地计算 Key 过期时间,而不需要通过 Master 服务器的帮助。
主从复制的作用
①保存 Redis 数据副本
当我们只是通过 RDB 或 AOF 把 Redis 的内存数据持久化毕竟只是在本地,并不能保证绝对的安全,而通过将数据同步 Slave 服务器上,可以保留多一个数据备份,更好地保证数据的安全。
②读写分离
在配置了主从复制之后,如果 Master 服务器的读写压力太大,可以进行读写分离,客户端向 Master 服务器写入数据,在读数据时,则访问 Slave 服务器,从而减轻 Master 服务器的访问压力
③高可用性与故障转移
服务器的高可用性是指服务器能提供 7*24 小时不间断的服务,Redis 可以通过 Sentinel 系统管理多个 Redis 服务器。
当 Master 服务器发生故障时,Sentineal 系统会根据一定的规则将某台 Slave 服务器升级为 Master 服务器,继续提供服务,实现故障转移,保证 Redis 服务不间断。
小结:Redis 的主从复制可以让我们把 Redis 中的数据同步到其他服务器上,为数据安全提供更加安全的保障,也可以让我们的服务器在发生故障而无法重启时,可以更加快速地切换服务器,继续对外提供服务。
作者:张君鸿
编辑:陶家龙、孙淑娟
出处:https://juejin.im/user/5c6665476fb9a049a81fd8e9