redis的持久化和主从哨兵架构解析

redis持久化

RDB(快照持久化)

RDB快照(snapshot), 在默认情况下, redis将内存数据库快照保存在名字为dump.rdb的文件中.

可以对redis进行设置, 让它在N秒内数据集至少有M个改动这一条被满足时, 自动保存一次数据集: save 60 1000, 就代表让redis在满足"60秒内至少有1000个键被改动"这一条件时, 就自动保存一次数据集快照. 同时, 这个save可以配置多个, 也就是可以支持多种rdb策略. 注意在配置中配置这种策略引起的save, 都是bgsave, 并不会阻塞客户端执行命令的线程.

image.png

在配置中还有两项与rdb有关, rdbfilename指定dump文件名, dir指定rdb的路径.

image.png

image.png

还可以手动执行命令生成rdb快照, 进入redis客户端, 执行命令save或bgsave可以生成dump.rdb文件, 每次执行都会将所有内存快照存到新的rdb文件里, 并覆盖原有的rdb文件.

bgsave的写时复制(COW)

redis借助操作系统提供的写时复制技术, 在生成快照的同时, 依然可以正常处理写命令. 因为bgsave子进程是由主进程fork生成的, 可以共享主线程的所有内存数据. bgsave子线程运行后, 开始读取主线程的内存数据, 并把它们写入rdb文件. 此时如果主线程对这些数据都是读操作, 那么两者互不影响, 如果此时主线程修改一块数据的话, 那么这块数据将会被复制一份, 生成该数据的副本, 然后bgsave子线程会把这个副本数据写入rdb文件中, 在这个过程中, 主线程仍然可以直接修改原来的数据.

rdb的优点

rdb恢复速度快, 因为rdb中存放的是内存的快照, 是二进制文件, 读取会非常快.

rdb的缺点

虽然rdb恢复很快, 但是rdb每次持久化都是全量持久化, 因为它保存的是当前数据的快照, 如果内存中有8G的数据, 那么redis就会把这8G的内存直接全量写到磁盘里的rdb快照文件里去, 这显然性能是非常低的.

rdb功能并不耐久, 如果redis因为某些原因而故障停机, 那么服务器将丢失最近写入, 且仍然未保存到快照中的那些数据.

AOF(append-only file)

由于rdb的局限性, redis增加了一种完全耐久的持久化方案: AOF持久化, 将修改的每一条指令记录进文件appendonly.aof中(先写入os cache, 每隔一段时间fsync到磁盘).

开启appendonly

image.png

写入一条命令 set darkness 123

image.png

再查看appendonly.aof文件

image.png

确实发现该命令已经写入到了aof文件中.

*2
$6
SELECT
$1
0
*3
$3
set
$8
darkness
$3
123
复制代码

这是一种resp协议格式的数据, *后面的数字代表命令有多少个参数, 后面的数字代表这个参数有几个字符 , 比如 s e t d a r k n e s s 123 命令 , \* 3 开始 , 代表命令有 3 个参数 , 第一个 后面的数字代表这个参数有几个字符, 比如set darkness 123命令, 从\*3开始, 代表命令有3个参数, 第一个 3代表set是3个字符, 第二个 8 代表 d a r k n e s s 8 个字符 , 第三个 8代表darkness是8个字符, 第三个 3代表123有3个字符.

注意: 如果执行带过期时间的set命令, aof文件里记录的并不是执行的原始命令, 而是记录key过期的时间戳. 比如执行 set name darkness ex 1000, 对应AOF文件如下, 可以看到, \*后面还是3, 而不是5, 并且命令也被改成了pexpireat了.

*3
$3
set
$4
name
$8
darkness
*3
$9
PEXPIREAT
$4
name
$13
1647523736190
复制代码

打开AOF功能后, 从打开后开始, 每当redis执行一个改变数据集的命令时, 比如set, 这个命令就会被追加到AOF文件的末尾. 当redis重新启动时, 程序就可以通过重新执行AOF文件中的命令来达到重建数据集的目的.

可以配置redis将数据fsync到磁盘的策略(频率)

appendfsync always: 每次有新的修改命令就追加到AOF文件时就执行一次fsync, 非常慢, 但是最安全
appendfsync evernsec: 每秒fsync一次, 足够快, 并且在故障时只会丢失1秒钟的数据.
appendfsync no: 从不fsync, 将数据交给操作系统来处理, 更快, 也更不安全.

推荐(也就是默认)的措施为每秒fsync一次, 这种fsync的策略可以兼顾速度和安全性
复制代码

AOF重写

AOF文件里可能有太多没用的指令, 所以AOF会定期根据内存最新数据生成AOF文件.

比如执行了如下几条命令:

image.png

然后客户端执行命令bgrewriteaof重写AOF

image.png

*3
$3
SET
$2
readcount
$1
5
复制代码

如下两个额配置可以控制AOF自动重写频率

auto-aof-rewrite-min-size 64mb: aof文件至少要达到64M才会自动重写, 文件太小恢复速度本来就很快, 重写意义不大
auto-aof-rewrite-percentage 100: aof文件自上一次重写后文件大小增长了100%才会再次触发重写
复制代码

注意: aof重写的时候, redis会fork出一个子进程去做, 不会对redis正常命令的处理有太多影响.

aof的优点

aof基本不会丢失数据, 并且每次都会保存命令, 不至于在内存庞大的时候去全量保存, 这样就不会导致因为数据集增长太快而为了持久化影响命令执行的速度.

aof的缺点

aof因为保存的是命令集, 因此每当重新加载持久化数据的时候, 相当于把整个redis之前的运行期间的所有命令重放一遍, 效率自然非常低下, 虽然aof提供了aof重写机制合并了诸多命令, 但是这个性能的痛点还是无法只靠这点手段就能解决的.

redis4.0混合持久化

上文提到, aof在启动加载持久化数据重放命令时, 会消耗大量时间, 导致redis启动会非常慢, 但是如果使用rdb又会导致数据丢失的风险过大, 因此redis4.0之后启用了另一种方案, 就是混合持久化, 通俗点讲就是aof+rdb混用.

通过如下配置启用混合持久化(必须先开启aof)

aof-use-rdb-preamble yes
复制代码

混合持久化是以aof为主的, 因为aof不会丢失数据, 因此依然还是使用aof去记录每一条命令, 但是与只使用aof不同的是, aof在重写时(也就是上文提到的aofrewrite), 不再是单纯的将内存数据转换为resp命令写入aof文件, 而是将重写这一刻之前的内存做rdb快照处理, 并且将rdb快照内容和增量的aof修改内存数据的命令存在一起, 都写入新的aof文件, 新的文件一开始不叫appendonly.aof, 等到重写完新的aof文件才会进行改名, 覆盖原有aof文件, 完成新旧两个aof文件的替换.

于是在redis重启的时候, 可以先加载rdb的内容, 然后再重放增量aof日志就可以完全替代之前的aof全量文件重放, 因此重启效率大大提升.

比如执行了set name darkness的时候刚好触发了aofrewrite, 在触发之后又执行了set darkness name, 那么重写的aof文件将会是把(含)set name darkness之前的, 上次已经rewrite之后的内存数据全量做rdb快照处理, 然后再将set darkness name做resp命令加在后面, 合并成一个新的appendonly.aof文件.

混合持久化aof文件结构如下:

image.png

redis数据备份策略

  1. 写crontab定时调度脚本, 每小时都copy一份rdb或aof的备份到一个目录中去, 仅仅保留最近48小时的备份.

  2. 每天都保留一份当日的数据备份到一个目录中去, 可以保留最近一个月的备份.

  3. 每次copy备份的时候, 把太旧的备份删除.

  4. 每天晚上将当前机器上的备份赋值一份到其它机器上, 以防机器损坏.

redis主从架构

image.png

redis主从原理

如果为master配置了一个slave, 不管这个slave是否是第一次连接上master, 它都会发送一个PSYNC命令给master请求复制数据.

master收到PSYNC命令后, 会在后台进行数据持久化通过bgsave生成最新的rdb快照文件, 持久化期间, master会继续接收客户端的请求, 它会把这些可能修改的数据集的请求缓存在内存中. 当持久化进行完毕以后, master会把这份rdb文件数据集发送给slave, slave会把接收到的数据进行持久化生成rdb, 然后再加载到内存中. 然后, master再将之前缓存在内存中的命令发送给slave.

当master与slave之间的连接由于某种原因断开时, slave能够自动重连master, 如果master收到了多个slave并发的连接请求, 它只会进行一次持久化, 而不是一个连接一次, 然后把这份持久化的数据发送给多个并发连接的slave.

主从复制(全量复制)

  1. psync命令同步数据, 发送命令之前会跟master建立socket长连接.

  2. master: 收到psync命令, 执行bgsave生成最新rdb快照数据.

  3. master: master开始做rdb之后会继续缓存新的数据, 其实就是一些写命令, 被称为repl buffer.

  4. master: 向slave发送rdb数据.

  5. slave: 清空老的数据并加载主节点rdb.

  6. master: 发送缓存数据repl buffer.

  7. slave: 执行buffer里的写命令到内存.

  8. master: 通过socket长连接持续把命令发送给从节点, 保证主从数据一致性.

主从复制(部分复制/断点续传)

当master和salve断开重连后, 一般都会对整份数据进行复制. 但是从redis2.8开始, redis改用可以支持部分数据复制的命令PSYNC去master同步数据, slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传).

master会在其内存中创建一个复制数据用的缓存队列, 缓存最近一段时间的数据, master和它所有的slave都维护了复制的数据下标offset和master的进程id, 因此, 当网络连接断开后, slave会请求master继续进行未完成的复制, 从所记录的数据下标开始. 如果master进程id变化了, 或者从节点数据下标offset太旧, 已经不在master的缓存队列里了, 那么将会进行一次全量复制.

  1. slave: 和master连接断开.

  2. master: master这时候仍然在继续缓存新的数据集(其实就是一些写命令), 称为repl backlog buffer.

  3. slave: 网络恢复, 重新与master建立socket长连接.

  4. slave: 向master发送 PSYNC(offset)命令.

  5. master: 判断slave发送的offset, 如果offset在repl backlog buffer中, 那么master将会把缓存中的在offset之后的数据一次性同步给slave节点. 如果不在, 就全量同步.

  6. master: 通过socket长连接持续把命令发送给从节点, 保证主从数据一致性.

主从复制风暴

如果一台主master有很多个从节点, 那么当这些从节点并发地向主节点发送PSYNC时, 主节点同步数据将会面临巨大的压力, 因为它要把数据同步给每个从节点. 因此如果有太多从节点的时候, 可以让部分从节点从部分从节点中同步数据, 如下图所示.

image.png

搭建一个1主2从的redis

下载redis-5.0.14, 下载链接

安装:

/****** 配置master ******/
// 创建redis6379目录
mkdir redis6379
// 解压下载的压缩包
tar -zxvf redis-5.0.14
// 进入解压后的redis根目录
cd redis-5.0.14
// 使用make安装, PREFIX=当前目录pwd的输出
make PREFIX=/opt/redis/redis6379/redis-5.0.14 install
// 配置redis.conf
protected-mode no
port 6379
daemonize yes
bind 0.0.0.0
pidfile "/var/run/redis_6379.pid"
appendonly yes
aof-use-rdb-preamble yes
/****** 配置master end ******/

/****** 配置slave ******/
// 创建目录redis6380
mkdir redis6380
// make安装和上面都一样, 配置需要有所不同
// 复制6379的redis.conf, 并修改部分配置
port 6380
replicaof 127.0.0.1 6379
replica-read-only yes
复制代码

至此已经都安装好了, 接下来启动

进入redis6379目录, 在redis根目录下输入: bin/redis-server redis.conf

image.png

进入redis6380目录, 在redis根目录下输入: bin/redis-server redis.conf

image.png

进入redis6381目录, 在redis根目录下输入: bin/redis-server redis.conf

image.png

查看是否都已经启动: ps -ef | grep redis

image.png

使用客户端进入主节点, info replication命令查看主从信息: bin/redis-cli -p 6379

可以看到具有2个连接的slave, 6379的role为master, 两个slave具体的ip端口也能展示的很清晰.

image.png

测试一下master节点数据能否正常同步给slave节点

  1. 先连接slave节点, 测试写入和读取, 发现不可写入, 读取testkey读取不到数据.

  2. 连接master节点, 写入testkey值为123.

  3. 连接6380和6381, 发现已经可以读取testkey了

image.png

redis哨兵架构

sentinel哨兵是特殊的redis服务, 不提供读写服务, 主要用于监控redis的实例节点.

哨兵架构下, client第一次从哨兵找出redis的主节点, 后续就直接访问redis的主节点, 不会每次都通过sentinel代理访问redis的主节点. 当redis的主节点发生变化, 哨兵会感知到, 并且将会选举出一个新的主节点通知给client端. 因此如果要使用sentinel架构的话, client是必须实现redis订阅功能的, 去订阅sentinel发布的节点变动消息.

image.png

搭建一个3节点集群redis哨兵

  1. 创建sentinel1, sentinel2, sentinel3三个文件夹, 将下载好的redis包丢进去, 然后解压, make安装.

  2. 进入redis根目录, 会看到一个sentinel.conf文件, 修改下面几个属性

protected-mode no
bind 0.0.0.0
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
// 下面这个配置3个sentinel节点建议都用同一个目录, 本人测试如果目录不同集群构建不起来
dir "/opt/redis/sentineldata"
// 下面这个名字如果不是mymaster, 启动会报错, 因为这个名字还有其它引用, 如果要改就全部得改
// 下面这个2是指: 哨兵集群中至少得有2个哨兵节点认为master挂了, 才会去重新选举一个新的master
sentinel monitor mymaster 127.0.0.1 6379 2
复制代码

如此修改3个sentinel.conf文件, 逐一启动

启动命令: bin/redis-sentinel sentinel.conf

image.png

验证哨兵集群启动成功: vim sentinel.conf, shift+G翻到最下面, 可以看到sentinel.conf已经加载了哨兵集群信息以及redis主从节点(哨兵集群中每个哨兵节点都会有).

image.png

测试哨兵集群是否能正常选举

  1. 干掉6379的redis

  2. 不停cat sentinel.conf, 会发现十几秒之后, conf中的主从信息变化了.

image.png

6379变为了从节点, 当然就代表6381就成了主节点了.

  1. 测试6381是否能执行写命令, 并查看主从info, 发现6381已经可以写命令了, 而且从节点只有6380没有6379, 这是因为现在6379还没有启动起来.

image.png

  1. 启动6379, 6379将会自动变为从节点

image.png

哨兵选举之后修改的配置文件

  1. 修改了每个哨兵节点的配置, 如sentinel.conf文件末尾的主从信息, 以及主节点配置. 下图中我们最开始配置的是6379, 但是经过选举之后, sentinel.conf中的monitor变为了6381.

image.png

  1. 修改了redis主从的每个节点的redis.conf, 如replicaof配置.
  • 6379增加replicaof 6381

image.png

  • 6380修改replicaof为6381

image.png

  • 6381删除了replicaof属性

image.png

因为修改了真实的redis-server的启动配置, 所以当6379启动的时候, 直接就是从节点了.

おすすめ

転載: juejin.im/post/7076303403369889806