学习笔记—分布式缓存(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 |
|
此外,RDB也可以做其他的一些配置在redis.conf中,比如是否压缩:
1 |
|
这个一般是不建议开启的,因为磁盘不值钱,很多,但是压缩是很消耗CPU的。
还有配置RDB文件的名称:
1 |
|
以及配置文件保存的路径目录:
1 |
|
RDB的原理
对于RDB来说,bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
这个fork采用了copy-on-write的技术,也就是说:
首先,主进程fork得到子进程,对于子进程来说,fork完后,读取内存数据,写新的RDB文件并且替换掉旧的RDB文件。
而对于主进程来说,如果执行的是读操作,那么就可以访问共享内存。
如果是执行的写操作,就会拷贝一份数据再进行写操作。
RDB持久化的配置
对于RDB的配置来说,需要对Redis安装目录下的redis.conf进行配置。首先,RDB是默认开启的,但也需要有一些基本的配置,以下是一个配置RDB持久化的示例片段:
1 |
|
其中,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 |
|
另外,如果想要开启AOF,需要先将RDB停用掉。如上述可以采用
1 |
|
的这么一种方式进行停用。
记录命令的频率appendfsync有三种策略情况:
策略名称 | 描述 | 优点 | 缺点 |
---|---|---|---|
no | 不进行fsync,操作系统决定何时刷新数据到磁盘。 | 最高性能,因为避免了磁盘I/O操作。 | 系统崩溃时可能会丢失大量数据。 |
everysec | 每秒执行一次fsync操作。 | 在性能和数据安全性之间取得了平衡,最多丢失1秒的数据。 | 系统崩溃时可能会丢失1秒内的数据,性能略低于”no”。 |
always | 每次写入操作后都执行fsync。 | 提供了最高的数据安全性,几乎不会丢失数据。 | 性能影响最大,因为每次写入都要进行磁盘I/O操作。 |
另外,由于是记录命令,所以AOF文件会比RDB文件大很多,并且,AOF由于是记录命令,再重新执行,对于同一个key的多次写操作来说,只有最后一次操作才是有意义的操作,因此,可以用bgrewriteaof这样一个命令,来让AOF文件执行重写功能,以期待用最少的命令达到相同的效果。
1 |
|
如上所示,重写命令。当然Redis在触发阈值后也会自动去重写AOF文件,这个阈值也是可以在redis.conf进行配置的:
1 |
|
AOF对比RDB
特性/方式 | AOF | RDB |
---|---|---|
数据安全性 | 更高,可以配置为每条写命令后同步 | 较低,取决于保存频率 |
恢复速度 | 较慢,因为需要重放所有命令 | 快速,直接载入数据快照 |
文件大小 | 通常比RDB大,因为记录了所有写命令 | 较小,只保存某一时刻的数据快照 |
写操作性能影响 | 较高,取决于同步策略 | 较低,只有在保存时影响 |
重写/保存机制 | 后台重写,不阻塞主线程 | 保存时阻塞主线程 |
数据丢失风险 | 最多丢失1秒内的数据(取决于配置) | 最多丢失最后一次快照后的数据 |
配置复杂度 | 相对复杂,有多种同步策略 | 相对简单,只需设置保存间隔和时间 |
各自适用的场景:
方式 | 适用场景 |
---|---|
AOF | 需要高数据安全性的场景,能够接受较慢的恢复速度和较大的文件体积,例如需要保证数据完整性的应用。 |
RDB | 需要快速恢复和较小文件体积的场景,能够接受一定时间内数据丢失的风险,例如缓存或对数据完整性要求不高的应用。 |
Redis的主从复制
为什么需要Redis主从
单节点的Redis并发是有上限的,如果要进一步提高Redis的并发能力,就需要通过读写分离,需要搭建主从集群。由master节点进行写操作,而采用一群从节点进行读操作,主节点和从节点之间保证数据的同步。
主从复制的配置
主从复制的配置是非常简单的,在配置文件中,只需要在从节点配置文件中添加如下配置即可:
1 |
|
当然也可以通过命令行进行配置:
1 |
|
需要注意的是,如果redis有密码,需要设置一下密码,可以在配置文件中设置:
1 |
|
也可以在命令行中设置:
1 |
|
例如,现在搭建了一个主节点在127.0.0.1:6379,另外启动一个docker为127.0.0.1:6378:
主从复制的原理
全量同步
当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 |
|
这样,master只需要同步给slave1,slave1再同步给slave2,slave2再同步给slave3,slave3再同步给slave4,这样master的压力就小了很多。
另外,就是使用pipeline,pipeline可以减少网络交互的次数,提升性能,但是pipeline的长度不能太大,否则会占用过多的内存。
最后,就是使用无盘复制,也就是直接从内存中读取RDB文件,而不是先写入磁盘再读取,这样可以减少磁盘IO,提升性能:
1 |
|