Redis6之(七)Redis集群—主从复制、哨兵模式、集群
一、Redis主从复制
1.1 主从复制简介
1.1.1 主从复制是什么?
通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据。但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。
简言之:为了避免单点故障,其他服务器依然可以继续提供服务。
复制功能可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
主机数据更新后根据配置和策略,自动同步到备机的 master/slaver机制,master以写为主,slave已读为主。
1.1.2 主从复制能干什么?
-
保存Redis数据副本
当我们只是通过 RDB或 AOF把 Redis的内存数据持久化毕竟只是在本地,并不能保证绝对的安全,而
通过将数据同步到 slave服务器上,可以保留多一个数据备份,更好地保证数据的安全
。 -
读写分离
在配置了主从复制之后,如果 master服务器的读写压力太大,可以进行读写分离,客户端向 master服务器写入数据,在读数据时,则访问 slave服务器,从而减轻 master服务器的访问压力。
-
高可用性与故障转移
服务器的高可用性是指服务器能提供7*24小时不间断的服务,Redis可以通过 Sentinel系统管理多个 Redis服务器,当master服务器发生故障时,Sentineal系统会根据一定的规则将某台 slave服务器升级为 master服务器,继续提供服务,实现故障转移,保证Redis服务不间断。
1.1.3 配置
在复制的概念中,数据库分为两类,一类是主数据库(master)
,另一类是从数据库(slave)
。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。
一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
1.2 主从复制步骤
-
配置文件内容解读
# 表示开启守护进程的方式 daemonize yes # 指定pidfile文件的路径及名字 pidfile /var/run/redis_6379.pid # 指定端口号 port 6379 # 指定dump.rdb名字 dbfilename dump6379.rdb # 关闭aof持久化 appendonly no
-
新建配置文件(3个)
-
启动三台redis服务器
-
查看三台服务器运行情况
默认都是主机。
-
配置主机和相关从机(配从不配主)
# 成为某个实例的从服务器 slaveof ip port
1.3 常用应用
1.3.1 一主二仆
-
从机从哪里开始复制数据?
现在有三台服务器:主机6379,从机6380和从机6381。在主机6379上设值,从机6380和6381同步数据。现在重新建立一台服务器6382,并将其设置为从机,在主机6379上设值 k4—v4:
服务器被设置为从机时,主机中的数据全部同步至从机中。
-
从机是否可以set数据
在从机中试图去 set数据:
从机只可读不可写。
-
主机 shutdown后,从机的状态?
模拟主机6379挂掉后,查看从机的状态:
主机挂掉,从机原地待命。
-
主机重新连接后,从机还能否复制?
重新连接主机并在主机中设值,查看从机中的数据:
主机重新连接,从机恢复数据同步。
1.3.2 薪火相传
上一个 slave可以是下一个 slave的 master,slave同样可以接收其他 slaves的连接和同步请求:
master是主机,slave1和 slave2是master的从机,slave11和 slave12是 slave1的从机,slave21和 slave22是 slave2的从机。
中途变更转向:会清除之前的数据,重新建立拷贝最新的数据。
风险:一旦某个 slave宕机,后面的 slave都无法备份。主机挂了,从机还是从机,无法写数据了。
1.3.3 反客为主
当一个 master宕机后,后面的 slave可以立刻升级为 master,其后面的 slave不用做任何修改。
# 将从机变为主机
slaveof no one
1.4 主从复制原理
当一个从数据库启动后,会向主数据库发送 SYNC 命令。同时主数据库接收到 SYNC命 令后会开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到的命令缓存起来。当快照完成后,Redis会将快照文件和所有缓存的命令发送给从数据库。从数据库收到后,会载入快照文件并执行收到的缓存的命令。以上过程称为复制初始化
。复制初始化结束后,主数据库每当收到写命令时就会将命令同步给从数据库,从而保证主从数据库数据一致。
当主从数据库之间的连接断开重连后,Redis 2.6以及之前的版本会重新进行复制初始化 (即主数据库重新保存快照并传送给从数据库),即使从数据库可以仅有几条命令没有收到,主数据库也必须要将数据库里的所有数据重新传送给从数据库。这使得主从数据库断线重连后的数据恢复过程效率很低下,在网络环境不好的时候这一问题尤其明显。Redis 2.8版 的一个重要改进就是断线重连能够支持有条件的增量数据传输,当从数据库重新连接上主数据库后,主数据库只需要将断线期间执行的命令传送给从数据库,从而大大提高Redis复制的实用性。
1.5 增量复制
在介绍复制的原理时提到当主从数据库连接断开后,从数据库会发送SYNC命令来重新进行一次完整复制操作。这样即使断开期间数据库的变化很小(甚至没有),也需要将数据库中的所有数据重新快照并传送一次。在正常的网络应用环境中,这种实现方式显然不太理想。Redis 2.8版相对2.6版的最重要的更新之一就是实现了主从断线重连的情况下的增量复制。
增量复制的条件
增量复制是基于如下3点实现的:
- 从数据库会存储主数据库的运行ID(run id)。每个 Redis运行实例均会拥有一个唯一的运行ID,每当实例重启后,就会自动生成一个新的运行ID;
- 在复制同步阶段,主数据库每将一个命令传送给从数据库时,都会同时把该命令存放到一个积压队列(backlog)中,并记录下当前积压队列中存放的命令的偏移量范围;
- 同时,从数据库接收到主数据库传来的命令时,会记录下该命令的偏移量。
这3点是实现增量复制的基础。在复制原理中可以看到,当主从连接准备就绪后,从数据库会发送一条 SYNC 命令来告诉主数据库可以开始把所有数据同步过来了。而 2.8 版之后,不再发送 SYNC命令,取而代之的是发送 PSYNC,格式为"PSYNC主数据库的运行 ID 断开前最新的命令偏移量"。
判断是否可以执行增量复制
主数据库收到 PSYNC命令后,会执行以下判断来决定此次重连是否可以执行增量复制:
- 首先
主数据库会判断从数据库传送来的运行ID是否和自己的运行ID相同
。这一步骤的意义在于确保从数据库之前确实是和自己同步的,以免从数据库拿到错误的数据
(比如 主数据库在断线期间重启过,会造成数据的不一致); - 然后
判断从数据库最后同步成功的命令偏移量是否在积压队列中
,如果在则可以执行增量复制,并将积压队列中相应的命令发送给从数据库。 如果此次重连不满足增量复制的条件,主数据库会进行一次全部同步(即与Redis 2.6的 过程相同)。
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中;
增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步。
二、哨兵模式
2.1 哨兵模式简介
2.1.1 引入哨兵模式
反客为主的自动版
在一个典型的一主多从的 Redis 系统中,从数据库在整个系统中起到了数据冗余备份和读写分离的作用。当主数据库遇到异常中断服务后,开发者可以通过手动的方式选择一个从数据库来升格为主数据库,以使得系统能够继续提供服务。然而整个过程相对麻烦且需要人工介入,难以实现自动化。 为此,Redis 中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。
2.1.2 什么是哨兵
顾名思义,哨兵的作用就是监控Redis系统的运行状况(此时不仅哨兵会同时监控主数据库和从数据库,哨兵之间也会互相监 控)。它的功能包括以下两个:
- 监控主数据库和从数据库是否正常运行;
- 主数据库出现故障时自动将从数据库转换为主数据库。
2.2 使用步骤
-
建立一主二仆的模式
主机6379带着两个从机6380和6381:
-
新建哨兵配置文件
在/myredis目录下新建文件:sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 1
-
启动哨兵模式
redis-sentinel /myredis/sentinel.conf
-
模拟哨兵模式推举新的主机
模拟当主机挂掉时:
如何推选新的主机?
根据优先级别:replica-priority 100,值越小优先级越高。
原主机重启后会变为从机。
2.3 故障恢复
从下线的主服务的所有服务里面挑选一个从服务,将其转成主服务,条件一次为:
- 选择
优先级靠前
的(replica-priority 100,值越小优先级越高); - 选择
偏移量(获得原主机数据最全的)最大的
; - 选择
runid最小
的从服务(每个 redis实例启动后都会随机生成一个40位的 runid)。
三、集群
3.1 集群简介
3.1.1 什么是集群?
Redis 集群实现了对 Redis的水平扩容
,即启动 N个 redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
对 Redis 进行
水平扩容
,在旧版 Redis 中通常使用客户端分片
来解决这个问题,即启动多个 Redis 数据库节点
,由客户端决定每个键交由哪个数据库节点存储,下次客户端读取该键时直接到该节点读取。这样可以实现将整个数据分布存储在 N个数据库节点中,每个节点只存放总数据量的 1/N。但对于需要扩容的场景来说,在客户端分片后,如果想增加更多的节点,就需要对数据进行手工迁移,同时在迁移的过程中为了保证数据的一致性,还需要将集群暂时下线,相对比较复杂。
3.1.2 为什么要使用集群?
即使使用哨兵,此时的 Redis 集群的每个数据库依然存有集群中的所有数据,从而导致集群的总数据存储量受限于可用存储内存最小的数据库节点,形成木桶效应。由于Redis中的所有数据都是基于内存存储,这一问题就尤为突出了,尤其是当使用 Redis 做持久化存储服务使用时。
而客户端分片也有非常多的缺点:比如维护成本高,增加、移除节点较繁琐等等。
集群的特点在于拥有和单机实例同样的性能,同时在网络分区后能够提供一定的可访问性以及对主数据库故障恢复的支持。
另外集群支持几乎所有的单机实例支持的命令,对于涉及多键的命令(如MGET),如果每个键都位于同一个节点中,则可以正常支持,否则会提示错误。除此之外集群还有一个限制是只能使用默认的0号数据库,如果执行 select切换数据库则会提示错误。
哨兵与集群是两个独立的功能,但从特性来看哨兵可以视为集群的子集,当不需要数据分片或者已经在客户端进行分片的场景下哨兵就足够使用了,但如果需要进行水平扩容,则集群是一个非常好的选择。
3.2 配置集群
使用集群,只需要将每个数据库节点的 cluster-enabled配置选项打开即可。每个集群中至少需要3个主数据库才能正常运行。
-
删除持久化数据
# 删除rdb持久化数据 rm -rf dump.rdb # 删除aof持久化数据 rm -rf appendonly.aof
-
修改配置信息(配置基本信息)
include /myredis/redis.conf pidfile /var/run/redis_6379.pid port 6379 dbfilename dump6379.rdb # 打开集群模式 cluster-enabled yes # 设定节点配置文件名 cluster-config-file nodes-6379.conf # 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换 cluster-node-timeout 15000
-
复制多个配置文件
-
启动6个redis服务
redis-server redis6379.conf redis-server redis6380.conf redis-server redis6381.conf redis-server redis6389.conf redis-server redis6390.conf redis-server redis6391.conf
-
将6个节点合成一个集群
将节点合成集群默认在redis安装包下的/src路径下:
配置为:
一个主机一个从机的模式。
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
redis-cli --cluster create --cluster-replicas 1 ip:6379 ip:6380 ip:6381 ip:6389 127.0.0.1:6390 127.0.0.1:6391
注意:
这里的ip要使用真实的ip地址。 -
连接登录
普通方式登录
普通方式登录时,可能直接进入读主机,存储数据时会出现 moves重定向操作。所以,应该以集群方式登录。
redis-cli -p 端口号
采用集群策略连接
设置数据会切换到相应的主机。
redis-cli -c -p 端口号
-
查看集群信息
cluster nodes
-
redis cluster 如何分配这六个节点?
一个集群至少要有三个主节点。
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的 IP地址,每个从库和主库不在一个IP地址上。
3.3 插槽
3.3.1 插槽的简介
一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
由此可见,初始化集群时分配给每个节点的插槽都是连续的,但是实际上Redis并没有此限制,可以将任意的几个插槽分配给任意的节点负责。
3.3.2 键与插槽的关系
Redis 将每个键的键名的有效部分使用CRC16算法计算出散列值,然后取对16384的余数。这样使得每个键都可以分配到16384个插槽中,进而分配的指定的一个节点中处理。
这里键名的有效部分是指:
- 如果键名包含{ }符号,并且{ }之间有至少一个字符,则有效部分是指{ }之间的内容;
- 如果不满足上一条规则,那么整个键名为有效部分。
例如:键 name的有效部分为 name,键 {user}:name 的有效部分为 user。
3.3.3 在集群中录值和查值
在 redis-cli 中每次录入、查询键值,redis都会计算出该 key 应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应该前往的 redis实例地址和端口。
也就是说,如果命令涉及多个键(如mget)时,只有当所有键都位于一个节点 redis才能正常支持。
-
录值
利用键的分配规则,可以将所有相关的键的有效部分设置成同样的值使得相关键都能分配到同一个节点以支持多键操作:
-
查值
cluster keyslot key cluster countkeysinslot 插槽号 cluster getkeysinslot 插槽号 查询数量
3.3.4 插槽的分配
插槽的分配分为两种情况:
- 插槽之前没有被分配过,现在想分配给指定节点;
- 插槽之前被分配过,现在想移动到指定节点。
3.3.5 获取插槽对应的节点
实际上,当客户端向集群中的任意一个节点发送命令后,该节点会判断相应的键是否在当前节点中,如果键在该节点中,则会像单机实例一样正常处理该命令;如果键不在该节点中,就会返回一个 MOVE 重定向请求,告诉客户端这个键目前由哪个节点负责,然后客户端再将同样的请求向目标节点重新发送一次以获得结果。
可见加入了-c参数后,如果当前节点并不负责要处理的键,Redis命令行客户端会进行自动命令重定向
。而这一过程正是每个支持集群的客户端应该实现的。然而相比单机实例,集群的命令重定向也增加了命令的请求次数,原先只需要执行一次的命令现在有可能需要依次发向两个节点,算上往返时延,可以说请求重定向对性能的还是有些影响的。
为了解决这一问题,当发现新的重定向请求时,客户端应该在重新向正确节点发送命令的同时,缓存插槽的路由信息,即记录下当前插槽是由哪个节点负责的。这样每次发起命令时,客户端首先计算相关键是属于哪个插槽的,然后根据缓存的路由判断插槽由哪个节点负责。考虑到插槽总数相对较少(16384个),缓存所有插槽的路由信息后,每次命令将均只发向正确的节点,从而达到和单机实例同样的性能。
3.4 故障恢复
在一个集群中,每个节点都会定期向其他节点发送 PING 命令,并通过有没有收到回复来判断目标节点是否已经下线了。具体来说,集群中的每个节点每隔1秒钟就会随机选择5个节点,然后选择其中最久没有响应的节点发送PING命令。
如果一定时间内目标节点没有响应回复,则发起 PING 命令的节点会认为目标节点疑似下线(PFAIL)。疑似下线可以与哨兵的主观下线类比,两者都表示某一节点从自身的角度认为目标节点是下线的状态。与哨兵的模式类似,如果要使在整个集群中的所有节点都认为某一节点已经下线,需要一定数量的节点都认为该节点疑似下线才可以。
在集群中,当一个主数据库下线时,就会出现一部分插槽无法写入的问题。这时如果该主数据库拥有至少一个从数据库,集群就进行故障恢复操作来将其中一个从数据库转变成主数据库来保证集群的完整。选择哪个从数据库来作为主数据库的过程与在哨兵中选择领头哨兵的过程一样,都是基于Raft算法。
当某个从数据库当选为主数据库后,会通过命令 slaveof no one 将自己转换成主数据库,并将旧的主数据库的插槽转换给自己负责。如果一个至少负责一个插槽的主数据库下线且没有相应的从数据库可以进行故障恢复,则整个集群默认会进入下线状态无法继续工作。
如果想在这种情况下使集群仍能正常工作,可以修改配置:
cluster-require-full-coverage:no(默认为yes)
redis.conf中的参数:cluster-require-full-coverage
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉;
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
现在模拟主节点6379下线后(显示master:fail),再启动主节点6379:
主节点恢复后,主节点变成从机。
3.5 集群的优劣势
3.5.1 优势
- 实现扩容
- 分摊压力
- 无中心配置相对简单
3.5.2 劣势
- 多键操作是不被支持的
- 多键的Redis事务是不被支持的。lua脚本不被支持
- 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。