学习笔记—分布式缓存(1)—Redis的主从与持久化

  对于单机的redis来说,在实际业务的使用中,存在着一定的问题。主要包括四类:

  首先是数据丢失问题,服务重启,Redis作为一种在内存中进行储存的工具,会丢失数据。

  第二是并发能力问题,对于Redis来说,单节点的Redis并发能力虽然不错,但是并不能满足较高并发场景的情况。

  第三是故障恢复问题,假如说redis宕机了,服务不可用了,怎么恢复呢?这就需要一种自动的故障恢复手段。

  最后是储存能力问题,前面有提到,Redis是基于内存的一种缓存工具,单节点所能储存的数据量显然是无法满足海量数据需求的。

  因此,针对于Redis,对于不同的业务需求,不能局限于一种单机的使用,而是需要考虑许多内容,包括持久化、主动、哨兵、分片集群、多级缓存等等。

Redis的持久化

  对于Redis的持久化来说,有两种主要的持久化方案,分别是RDB持久化AOF持久化

RDB持久化

  RDB持久化,全称Redis Database Backup file,也叫Redis数据备份文件或者Redis数据快照,是一种将Redis在内存中的数据以快照的形式写入磁盘中的持久化方案。

  另外,RDB也是redis备份的默认方式

  简单来说,当Redis实例故障重启之后,从磁盘读取快照文件,恢复数据。

  这个快照文件就被称为RDB文件,默认是保存在Redis的当前运行目录的。

  这种方式的优点是,大规模的数据恢复、并且对于数据恢复的完整性要求不高的情况下,会更加高效;并且由于以二进制方式储存,占用的内存会更小;此外,Redis使用bgsave命令进行持久化,基本不会影响主进程,能保证redis的高性能。

  而缺点则在于,Fork的时候,内存中的数据会被克隆一份,大致2倍的膨胀,数据庞大时还是比较消耗性能;另外,在备份周期在一定间隔时间做一次备份,所以如果Redis意外down的话,就会丢失最后一次快照后所有修改。

RDB持久化的执行时机

  RBD持久化在四种情况下会执行。分别是:

  执行save命令的时候,会立即执行一次RDB。save命令会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。这个命令一般只有在数据迁移时可能用到。

  执行bgsave命令,这个命令会以异步的方式进行RDB,会开启一个独立进程完成RDB,主进程会持续处理用户请求,而不受影响。

  停机时,当停机的时候,Redis会执行一次save,来实现RDB持久化。

  此外,Redis内部也有触发RDB的机制,这个可以在redis.conf文件中找到并配置,一般是这种格式:

1
2
# 在m秒内,如果有至少n个key被修改,那么就会执行bgsave,如果是save "" 则表示禁用RDB持久化
save m n

  此外,RDB也可以做其他的一些配置在redis.conf中,比如是否压缩:

1
rdbcompression yes

  这个一般是不建议开启的,因为磁盘不值钱,很多,但是压缩是很消耗CPU的。

  还有配置RDB文件的名称:

1
dbfilename dump.rdb

  以及配置文件保存的路径目录:

1
dir ./

RDB的原理

  对于RDB来说,bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。

  这个fork采用了copy-on-write的技术,也就是说:

  首先,主进程fork得到子进程,对于子进程来说,fork完后,读取内存数据,写新的RDB文件并且替换掉旧的RDB文件。

  而对于主进程来说,如果执行的是读操作,那么就可以访问共享内存。

  如果是执行的写操作,就会拷贝一份数据再进行写操作。

RDB持久化的配置

  对于RDB的配置来说,需要对Redis安装目录下的redis.conf进行配置。首先,RDB是默认开启的,但也需要有一些基本的配置,以下是一个配置RDB持久化的示例片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# RDB持久化配置
save 900 1
save 300 10
save 60 10000

# RDB文件名
dbfilename dump.rdb

# RDB文件和AOF文件的存储目录
dir /var/lib/redis/

# 后台保存出错时停止写操作
stop-writes-on-bgsave-error yes

# 使用LZF压缩字符串对象
rdbcompression yes

# 使用CRC64算法进行数据校验
rdbchecksum yes

  其中,save 900 1表示在900秒内,如果至少有1个key被修改,那么就会执行bgsave,如果是save “” 则表示禁用RDB持久化。

  dbfilename dump.rdb表示RDB文件名,dir /var/lib/redis/表示RDB文件和AOF文件的存储目录。

  stop-writes-on-bgsave-error yes表示后台保存出错时停止写操作。

  rdbcompression yes表示使用LZF压缩字符串对象。

  rdbchecksum yes表示使用CRC64算法进行数据校验。

AOF持久化

  AOF全称为Append Only File(追加文件)。

  AOF的持久化是以独立日志的方式记录每次写的命令,重启时重新执行AOF文件中的命令恢复数据。

  换言之,可以看作是一个命令日志文件,里面记录着执行的命令记录。重启后,又重新执行一遍,数据就恢复了。

  AOF的配置默认是关闭的,也需要通过redis.conf进行修改:

1
2
3
4
5
6
7
8
9
10
# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no

  另外,如果想要开启AOF,需要先将RDB停用掉。如上述可以采用

1
save ""

  的这么一种方式进行停用。

  记录命令的频率appendfsync有三种策略情况:

策略名称 描述 优点 缺点
no 不进行fsync,操作系统决定何时刷新数据到磁盘。 最高性能,因为避免了磁盘I/O操作。 系统崩溃时可能会丢失大量数据。
everysec 每秒执行一次fsync操作。 在性能和数据安全性之间取得了平衡,最多丢失1秒的数据。 系统崩溃时可能会丢失1秒内的数据,性能略低于”no”。
always 每次写入操作后都执行fsync。 提供了最高的数据安全性,几乎不会丢失数据。 性能影响最大,因为每次写入都要进行磁盘I/O操作。

  另外,由于是记录命令,所以AOF文件会比RDB文件大很多,并且,AOF由于是记录命令,再重新执行,对于同一个key的多次写操作来说,只有最后一次操作才是有意义的操作,因此,可以用bgrewriteaof这样一个命令,来让AOF文件执行重写功能,以期待用最少的命令达到相同的效果。

1
2
3
4
5
6
7
8
9
10
# redis-cli中
bgrewriteaof
# 优化日志命令
set key 123
set key 456 --------> set key 777
set key 777

set key 111
set name gagaduck -------> mset name gagaduck key 777
set key 777

  如上所示,重写命令。当然Redis在触发阈值后也会自动去重写AOF文件,这个阈值也是可以在redis.conf进行配置的:

1
2
3
4
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb

AOF对比RDB

特性/方式 AOF RDB
数据安全性 更高,可以配置为每条写命令后同步 较低,取决于保存频率
恢复速度 较慢,因为需要重放所有命令 快速,直接载入数据快照
文件大小 通常比RDB大,因为记录了所有写命令 较小,只保存某一时刻的数据快照
写操作性能影响 较高,取决于同步策略 较低,只有在保存时影响
重写/保存机制 后台重写,不阻塞主线程 保存时阻塞主线程
数据丢失风险 最多丢失1秒内的数据(取决于配置) 最多丢失最后一次快照后的数据
配置复杂度 相对复杂,有多种同步策略 相对简单,只需设置保存间隔和时间

  各自适用的场景:

方式 适用场景
AOF 需要高数据安全性的场景,能够接受较慢的恢复速度和较大的文件体积,例如需要保证数据完整性的应用。
RDB 需要快速恢复和较小文件体积的场景,能够接受一定时间内数据丢失的风险,例如缓存或对数据完整性要求不高的应用。

Redis的主从复制

为什么需要Redis主从

  单节点的Redis并发是有上限的,如果要进一步提高Redis的并发能力,就需要通过读写分离,需要搭建主从集群。由master节点进行写操作,而采用一群从节点进行读操作,主节点和从节点之间保证数据的同步。

主从复制的配置

  主从复制的配置是非常简单的,在配置文件中,只需要在从节点配置文件中添加如下配置即可:

1
slaveof 127.0.0.1 6379

  当然也可以通过命令行进行配置:

1
slaveof 127.0.0.1 6379

  需要注意的是,如果redis有密码,需要设置一下密码,可以在配置文件中设置:

1
masterauth 123456

  也可以在命令行中设置:

1
config set masterauth 123456

  例如,现在搭建了一个主节点在127.0.0.1:6379,另外启动一个docker为127.0.0.1:6378:

Redis主从设置
Redis从节点显示1
Redis从节点显示2

主从复制的原理

全量同步

  当Redis主从第一次建立连接的时候,会执行一次全量同步,用于将master节点的所有数据都拷贝到slave节点。可分为三个阶段:

  第一阶段:从节点执行replicaof命令,开始建立连接,从Redis向主Redis请求数据同步,主Redis判断是否是第一次数据同步,如果是第一次数据同步,那么就向从Redis返回主节点的数据版本信息,从节点收到版本信息后保存下来。

  第二阶段:主节点执行bgsave,生成RDB,并且记录RDB期间所有的命令到一个repl_backlog中,而后发送RDB文件到从节点,从节点收到后,清空本地数据并加载RDB文件。

  最后,第三阶段,主节点将repl_backlog中的命令发送到从节点,从节点接收并执行。

  此外,对于主节点如何判断是否是第一次同步的问题,需要看一个replid是否一致。

  从节点发送自己的Replication Id(是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid)和offset(偏移量,随着记录在repl_baklog中的数据增多而逐渐增大,如果slave的offset小于master的offset,说明slave数据落后于master,需要更新)到主节点。

  对于主节点来说,判断这个请求的replid是否和自己的一致,如果不一致,说明现在的从节点还不是自己的从节点而是另外一个主节点,因此,需要发送主节点的replid和offset到从节点,让从节点保存下来从而从一个主节点变成一个从节点。

增量同步

  全量同步相当于做RDB,然后再把RDB传输到slave,这个成本实在是太高了,尤其是如果说RDB很大,就更高了,因此,一般来说,只有在初次同步会全量同步,其他时候应该选择增量同步。

  对于增量同步来说,流程一般如下:

  从节点发送自己的reolid和offset给主节点,由主节点判断是否一致,如果一致,那么说明是从节点并且不是初次同步,回复给从节点continue,并去repl_backlog中获取从节点传输过来的offset后面的数据,并发送offset后面的命令来让从节点执行,以此同步。

repl_backlog

  在全量同步和增量同步中,都提到了一个东西就是repl_backlog。

  对于主节点来说,怎么能知道从节点和自己的差异在哪里呢?就需要靠这个文件。

  repl_backlog这个文件是一个固定大小的环形数组,脚标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。

  在这个文件中,会记录Redis处理过的命令日志及offset,包括主节点当前的offset,和从节点已经拷贝到的offset。

  主从offset之间的差异,就是从节点需要拷贝增量的数据。

  由于是一个环形数组,所以会出现以下两种情况:

  第一种,从节点增量很及时,追上了主节点的进度,那么,这种情况下,就很正常的继续更新。

  第二种,如果说网络阻塞了,主节点的offset远远超过从节点了,那么当主节点继续写入数据,offset覆盖了旧的,把slave现在的offset也给覆盖了,那么这个时候,slave就无法通过增量同步了,因为连自己的offset在主节点都找不到了,拿什么新增数据呢?只能做全量同步了。

  换言之,repl_backlog是一个环形数组,有上限,写满了,就会覆盖最老的,如果从节点断开的时间太久或者阻塞太厉害,没有备份的数据就会被覆盖,那么就无法基于log来做增量同步了,只能全量同步RDB了。

主从同步的优化

  对于主从同步来说,可以从以下几个方面进行优化:

  首先自然是适当扩大repl_backlog的大小,这样能尽量避免全量同步,全量同步太花时间了。如果发现从节点宕机了及时恢复也是从这一方面的考虑。

  另外,就是限制从节点的数量,避免给主节点太多压力,如果确实需要很多从节点,那么考虑使用主-从-从的链式结构,一层层做同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
  master
|
v
slave1
|
v
slave2
|
v
slave3
|
v
slave4

  这样,master只需要同步给slave1,slave1再同步给slave2,slave2再同步给slave3,slave3再同步给slave4,这样master的压力就小了很多。

  另外,就是使用pipeline,pipeline可以减少网络交互的次数,提升性能,但是pipeline的长度不能太大,否则会占用过多的内存。

  最后,就是使用无盘复制,也就是直接从内存中读取RDB文件,而不是先写入磁盘再读取,这样可以减少磁盘IO,提升性能:

1
2
# 启用无盘复制
repl-diskless-sync yes

学习笔记—分布式缓存(1)—Redis的主从与持久化
https://gagaducko.github.io/2024/09/20/学习笔记—分布式缓存-1-—Redis的主从与持久化/
作者
gagaduck
发布于
2024年9月20日
许可协议