Redis可以通过将数据保存在磁盘里实现持久化功能, 以防止宕机导致的数据大量丢失.但是持久化只能保证在磁盘不损坏的情况下, 长时间保存数据, 如果一旦磁盘损坏, 数据仍然会丢失. 为了解决这个问题, 主从复制应允而生.
主从复制是Redis集群中的一总, 其中一个Redis做主, 其他Redis做从, 主对外能读能写, 并且会定期将数据复制到从节点, 而从节点对外只能被读. 这样, 就有多台主机保存相同的数据, 一旦主节点顺坏, 从节点的数据也不会受到影响, 而且从节点可以被外界读, 因此做到了读写分离, 也提高了并发能力. 但是如果主节点一旦宕机, 需要人手动将某个从节点升级为主节点, 因此系统的可用性不高.
哨兵模式则提高系统的可用性的, 当主节点宕机, 它可以自动将某个从节点升级为主节点, 不需要人工干预. 但是由于只有一个主节点, 主节点需要定时将数据复制到从节点, 如果从节点太多, 主节点的负载会很大, 因此横向扩展性不强.
Redis cluster集群则很好的解决了横向扩展性不强的问题. Redis cluster集群是去中心化的系统, 它有多个主节点, 每个主节点对应多个从节点. 主节点可读可写, 且写入的数据, 会被通过一定的算法分散到各个主节点中. 从节点则只是复制对应主节点的内容, 一旦某个主节点宕机, 对应的从节点中的一个就会自动升级为主节点.
一 Redis持久化
redis 的数据全部在内存中,如果突然宕机,数据就会全部丢失,因此需要持久化来保证 Redis 的数据不会因为故障而丢失,redis 重启的时候可以重新加载持久化文件来恢复数据;
redis有四种持久化的方式:aof,aof rewrite,rdb和混合持久化。默认配置下,只开启 rdb 持久化。
1 aof(append only file)
aof 日志存储的是 Redis 服务器的顺序指令序列,aof 日志只记录对内存修改的指令记录;
通过重放(replay)aof 日志中指令序列来恢复 Redis 当前实例的内存数据结构的状态;
刷盘的策略可以分为:
# 1. 每条命令刷盘
# appendfsync always
# 2. 每秒刷盘
# appendfsync everysec
# 3. 交由系统刷盘
# appendfsync no
这些可以在配置文件里配置
这种持久化方式的缺点:
随着时间越长,aof 日志会越来越长,如果 redis 重启,重放整个 aof 日志会非常耗时,导致redis 长时间无法对外提供服务。
2 aof rewrite
aof 持久化策略会持久化所有修改命令;里面的很多命令其实可以合并或者删除;aof rewrite 则是精简aof文件中的命令.
aof rewrite 在 aof 的基础上,满足一定策略则 fork 进程,根据当前内存状态以及现有的AOF文件,转换成一系列的 redis 命令,序列化成一个新的 aof 日志文件中,序列化完毕后再将操作期间发生的增量 aof 日志追加到新的 aof 日志文件中,追加完毕后替换旧的 aof 日志文件;以此达到对 aof 日志瘦身的目的。
note:aof rewrite 开启的前提是开启 aof。
配置方法:
# 开启 aof
appendonly yes
# 开启 aof复写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 关闭 混合持久化
aof-use-rdb-preamble no
# 关闭 rdb
save ""
策略:
# 1. redis 会记录上次aof复写时的size,如果之后累计超过了原来的size,则会发生aof复写;
auto-aof-rewrite-percentage 100
# 2. 为了避免策略1中,小数据量时产生多次发生aof复写,策略2在满足策略1的前提下需要超过 64mb
#才会发生aof复写;
auto-aof-rewrite-min-size 64mb
缺点:aof复写在 aof 基础上实现了瘦身,但是 aof 复写的数据量仍然很大;加载会非常慢
3 rdb
基于 aof 或 aof 复写文件大的缺点,rdb 是一种快照持久化, 它也是Redis的默认持久化方式。它通过 fork 主进程,在子进程中将内存当中的数据键值对按照存储方式持久化到 rdb 文件中。rdb 存储的是经过压缩的二进制数据。
配置:
# 关闭 aof 同时也关闭了 aof复写
appendonly no
# 关闭 aof复写
auto-aof-rewrite-percentage 0
# 关闭 混合持久化
aof-use-rdb-preamble no
# 开启 rdb 也就是注释 save ""
# save ""
# save 3600 1
# save 300 100
# save 60 10000
策略:
# redis 默认策略如下:
# 注意:写了多个 save 策略,只需要满足一个则开启rdb持久化
# 3600 秒内有以1次修改
save 3600 1
# 300 秒内有100次修改
save 300 100
# 60 秒内有10000次修改
save 60 10000
缺点:若采用 rdb 持久化,一旦 redis 宕机,redis 将丢失一段时间的数据。RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且 CPU 性能不是很好的情况下,这种情况会持续1秒,AOF 也需要 fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。
4 混合持久化
从上面知道,rdb 文件小且加载快但丢失多,aof文件大且加载慢但丢失少。混合持久化是吸取rdb 和 aof 两者优点的一种持久化方案。aof 复写的时候实际持久化的内容是 rdb,等持久化后,持久化期间修改的数据以 aof 的形式附加到文件的尾部。混合持久化实际上是在 aof rewrite 基础上进行优化。所以需要先开启 aof rewrite。
配置
# 开启 aof
appendonly yes
# 开启 aof复写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 开启 混合持久化
aof-use-rdb-preamble yes
# 关闭 rdb
save ""
# save 3600 1
# save 300 100
# save 60 10000
二 Redis主从复制
redis主从复制是集群的原理, 是为了提高数据的可靠性,和读写分离的。它是一种数据复制和同步机制,用于将一个 Redis 实例(主节点)的数据复制到其他 Redis 实例(从节点)。在主从复制中,主节点负责处理写操作,并将写操作的结果复制到一个或多个从节点,从节点则负责接收主节点发送的数据并将其复制到本地,从而保持与主节点数据的一致性。
主从复制的主要目的是实现数据的冗余备份、读写分离和横向扩展, 防止主 redis 所在磁盘损坏,造成数据永久丢失。主从之间采用异步复制的方式。从数据库有只读属性。
# redis.conf
replicaof 127.0.0.1 7002 #选择目标主节点
主从复制中的数据同步, 分为全量数据同步和增量数据同步, 它们分别是:
-
全量数据同步: 全量数据同步是在主从复制建立时进行的初始数据同步阶段。在这个阶段,主节点会将自己的完整数据集发送给从节点,以确保从节点拥有与主节点一致的数据。全量数据同步可以通过以下方式实现:
- 快照(Snapshot):主节点会生成一个快照文件,包含了当前的数据集,然后将快照文件发送给从节点。从节点接收到快照文件后,会加载文件并恢复数据。其优点是效率高, 缺点是延迟大, 对网络带宽和存储空间的要求高
- 命令传播(Command Replication):主节点会将执行在自己上面的写操作命令记录下来,并发送给从节点。从节点接收到命令后,按照顺序执行这些命令来恢复数据。其优点是实时性好, 数据延迟小, 缺点是效率低, 复杂性高, 需要处理命令的并发和一致性等问题.
选择哪种同步方式, 是通过配置文件进行设置的.
-
增量数据同步(Incremental Sync):在完成全量数据同步之后,主从节点之间会进行增量数据同步。增量数据同步是指主节点将写操作命令发送给从节点,以使从节点实时更新自己的数据,以保持与主节点的一致性。增量数据同步是异步的,主节点会将命令发送给从节点,但从节点执行命令的速度可能会受到网络延迟等因素的影响。在增量数据同步期间,如果从节点与主节点之间的连接断开,从节点会尝试重新连接并继续同步丢失的数据。
全量数据同步的流程图:
增量数据同步的流程图:
服务器 RUN ID:无论主库还是从库都有自己的 RUN ID,RUN ID 启动时自动产生,RUN ID 由40个随机的十六进制字符组成。当从库对主库初次复制时,主库将自身的 RUN ID 传送给从库,从库会将 RUN ID 保存。
当从库断线重连主库时,从库将向主库发送之前保存的 RUN ID。
- 从库 RUN ID 和主库 RUN ID 一致,说明从库断线前复制的就是当前的主库;主库尝试执行增量同步操作;
- 若不一致,说明从库断线前复制的主库并不时当前的主库,则主库将对从库执行全量同步操作。
复制偏移量: 是一个用于标识复制进度的值。它表示主节点和从节点之间的数据同步位置。主从都会维护一个复制偏移量, 当从节点连接到主节点进行复制时,它会向主节点发送一个复制请求,并提供自己的复制偏移量。主节点会将从节点设置为该偏移量之后的数据进行同步。
其同步方式是:
- 主库向从库发送N个字节的数据时,将自己的复制偏移量上加N。
- 从库接收到主库发送的N个字节数据时,将自己的复制偏移量加上N。
通过比较主从偏移量得知主从之间数据是否一致;偏移量相同则数据一致;偏移量不同则数据不一致。
环形缓冲区(复制积压缓冲区):本质就是固定长度先进先出队列;
存储内容:当因某些原因(网络抖动或从库宕机)从库与主库断开连接,避免重新连接后开始全量同步,在主库设置了一个环形缓冲区;该缓冲区会在从库失联期间累计主库的写操作;当从库重连,会发送自身的复制偏移量到主库,主库会比较主从的复制偏移量:
- 若从库offset还在复制积压缓冲区中,则进行增量同步;
- 否则,主库将对从库执行全量同步;
三 Redis哨兵模式
Redis的哨兵模式, 主要是是为了解决Redis集群的高可用性问题. 和高可用性相比, 能够自动切换宕机的主节点.
哨兵模式是由一个或多个 sentinel 实例构成 sentinel 系统。sentinal和redis, 是两个相互独立的程序. 该系统可以监视任意多个主库以及这些主库所属的从库。当主库处于下线状态,自动将该主库所属的某个从库升级为新的主库。
客户端来连接集群时,会首先连接 sentinel,通过 sentinel 来查询主节点的地址,然后再连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 sentinel 索要主库地址,sentinel 会将最新的主库地址告诉客户端。通过这样客户端无须重启即可自动完成节点切换。
哨兵模式当中涉及多个选举流程采用的是 Raft 算法的领头选举方法的实现;
原理图:
配置:
# sentinel.cnf
# sentinel 只需指定检测主节点就行了,通过主节点自动发现从节点
sentinel monitor mymaster 127.0.0.1 6379 2
# 判断主观下线时长
sentinel down-after-milliseconds mymaster 30000
# 指定可以有多少个Redis服务同步新的主机,一般而言,这个数字越小同步时间越长,而越大,则对网
# 络资源要求越高
sentinel parallel-syncs mymaster 1
# 指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟
sentinel failover-timeout mymaster 180000
异常检测:
主观下线:
sentinel 会以每秒一次的频率向所有节点(其他sentinel、主节点、以及从节点)发送 ping 消息,然后通过接收返回判断该节点是否下线;如果在配置指定 down-after-milliseconds 时间内则被判断为主观下线。
客观下线:
当一个 sentinel 节点将一个主节点判断为主观下线之后,为了确认这个主节点是否真的下线,它会向其他sentinel 节点进行询问,如果收到一定数量的已下线回复,sentinel 会将主节点判定为客观下线,并通过领头 sentinel 节点对主节点执行故障转移;
故障转移:
主节点被判定为客观下线后,开始领头 sentinel 选举,需要一半以上的 sentinel 支持,选举领头sentinel后,开始执行对主节点故障转移。
- 从从节点中选举一个从节点作为新的主节点
- 通知其他从节点复制连接新的主节点
- 若故障主节点重新连接,将作为新的主节点的从节点
使用方式:
- 连接一个哨兵节点,并且获取主节点信息;
SENTINEL GET-MASTER-ADDR-BY-NAME - 验证当前获取的主节点。
ROLE 或者 INFO REPLICATION - 为当前连接的哨兵节点,添加发布订阅( PUB/SUB )连接,并且订阅 +switch-master 频道。
缺点:
redis 采用异步复制的方式,意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分未同步的消息将丢失。如果主从延迟特别大,那么丢失可能会特别多。sentinel 无法保证消息完全不丢失,但是可以通过配置来尽量保证少丢失。
# 主库必须有一个从节点在进行正常复制,否则主库就停止对外写服务,此时丧失了可用性
min-slaves-to-write 1
# 这个参数用来定义什么是正常复制,该参数表示如果在10s内没有收到从库反馈,就意味着从库同步不正常;
min-slaves-max-lag 10
同时,它的致命缺点是不能进行横向扩展, 因为, 所有的写操作都需要通过组节点完成, Redis节点过多, 会导致master负载过高.
四 Redis cluster集群
Redis cluster 将所有数据划分为 16384(2^14)个槽位,每个 redis 节点负责其中一部分槽位。cluster 集群是一种去中心化的集群方式。
如图,该集群由三个 redis 节点组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样。这三个节点相互连接组成一个对等的集群,它们之间通过一种特殊的二进制协议交互集群信息。
当 redis cluster 的客户端来连接集群时,会得到一份集群的槽位配置信息。这样当客户端要查找某个 key时,可以直接定位到目标节点。
客户端为了可以直接定位(对 key 通过 crc16 进行 hash 再对2^14取余)某个具体的 key 所在节点,需要缓存槽位相关信息,这样才可以准确快速地定位到相应的节点。同时因为可能会存在客户端与服务器存储槽位的信息不一致的情况,还需要纠正机制(通过返回 -MOVED 3999127.0.0.1:6479 ,客户端收到后需要立即纠正本地的槽位映射表)来实现槽位信息的校验调整。
另外,redis cluster 的每个节点会将集群的
配置信息持久化到配置文件中,这就要求确保配置文件是可写的,而且尽量不要依靠人工修改配置文件。
数据迁移
redis cluster 提供了工具 redis-trib 可以让运维人员手动调整槽位的分配情况,它采用 ruby 语言开发,通过组合原生的 redis cluster 指令来实现。图中:A 为待迁移的源节点,B 为待迁移的目标节点。
过程:
如上图:redis 迁移的单位是槽,redis 是一个槽一个槽地进行迁移,当一个槽位正在迁移时,这个槽就处于中间过渡状态。这个槽在源节点的状态为 migrating ,在目标节点的状态为importing ,表示此时数据正在从源节点流向目标节点。
迁移工具 redis-trib 首先在源节点和目标节点设置好中间过渡状态,然后一次性获取源节点槽位的所有或者部分的 key 列表,再依次将 key 进行迁移。源节点对当前的 key 执行 dump 指令得到序列化内容,然后向目标节点发送 restore 指令,目标节点将源节点的序列化内容进行反序列化并将内容应用到目标节点的内容中,然后返回 +ok 给源节点,源节点收到后删除该 key;按照这些步骤将所有待迁移的 key 进行迁移。
note:迁移过程是同步的,迁移过程中源节点的主线程处于阻塞状态,直到key被删除。如果迁移过程中源节点出现网络故障,这两个节点依然处于中间状态,重启后,redis-trib可继续迁移。
所以,redis-trib 迁移的过程是一个一个 key 来进行,如果这个 key 对应 val 内容很大,将会影响到客户端的正常访问。
复制以及故障转移
cluster 集群中节点分为主节点和从节点,其中主节点用于处理槽,而从节点则用于复制该主节点,并在主节点下线时,代替主节点继续处理命令请求。
故障检测
集群中每个节点都会定期地向集群中的其他节点发送 ping 消息,如果接收 ping 消息的节点没有在规定时间内回复 pong 消息,那么这个没有回复 pong 消息的节点会被标记为 PFAIL(probablefail)。
集群中各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息;如果在一个集群中,半数以上负责处理槽的主节点都将某个主节点 A 报告为疑似下线,那么这个主节点A将被标记为下(FAIL);标记主节点 A 为下线状态的主节点会广播这条消息,其他节点(包括A节点的从节点)也会将A节点标识为 FAIL;
故障转移
当从节点发现自己的主节点进入FAIL状态,从节点将开始对下线主节点进行故障转移;
- 从数据最新的从节点中选举为主节点;
- 该从节点会执行 replica no one 命令,称为新的主节点;
- 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己;
- 新的主节点向集群广播一条 pong 消息,这条 pong 消息可以让集群中的其他节点立即知道
- 这个节点已经由从节点变成主节点,并且这个主节点已经接管了之前下线的主节点;
- 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移结束;
集群配置方法:
创建文件夹:
# 创建 6 个文件夹
mkdir -p 7001 7002 7003 7004 7005 7006
cd 7001
vi 7001.conf
# 7001.conf 中的内容如下
集群中单个Redis的配置方式:
pidfile "/home/chengjun/redis-data/7001/7001.pid"
logfile "/home/chengjun/redis-data/7001/7001.log"
dir /home/chengjun/redis-data/7001/
port 7001
daemonize yes
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 15000
将配置复制多份, 并启动多个Redis进程.
手动创建集群:
# 节点会面
cluster meet ip port
# 分配槽位
cluster addslots slot
# 分配主从
cluster replicate node-id
智能创建集群:
redis-cli --cluster help
# --cluster-replicas 后面对应的参数 为 一主对应几个从数据库
redis-cli --cluster create host1:port1 ... hostN:portN --cluster-replicas
<arg>
redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:链接