Redis 高可用
Redis 主节点宕机后,从节点替换。若该工作由 Redis 来完成,则说明 Redis 是高可用的。
Redis 如何实现高可用
- 主从复制:分布式一致性解决
- 主从切换:集群 Cluster,哨兵 Sentinel
高可用的程度由主从切换的速度来决定,如果时间在秒级,则可以说高可用。
1、主从复制
主从复制是数据同步方式,解决了单点故障的问题,不能保证高可用。同时,主从库间采用读写分离的方式,主库可以进行读写操作,当发生写操作自动将写操作同步从库;从库一般是只读,接受主库同步过来的写操作命令,然后执行这条命令。也就是说,所有的数据修改只在主服务器上进行,然后将最新的数据同步给从服务器,这样就使得主从服务器的数据是一致的。
读写分离
1.2、数据同步方式
- 全量复制:主从第一次同步,主库发送所有数据(生成 RDB 文件和传输 RDB 文件)到从库
- 基于长连接的命令传播:第一次同步完成后,主从维护着一个长连接,主库收到写操作命令后,通过这个连接将写命令传播给从库,保证主从库的数据一致性
- 增量复制:从库断开重连,主库补发丢失数据到从库
redis 主从复制
1.2、主从复制的实现
runid
用于验证主从之间的关系,每个 Redis 节点启动时都生成 runid。
从库对主库初次复制时,主库将自己的 runid 传递给从库,从库将其保存。当从库断线重连主库时,从库将向主库发送之前保存的 runid
- 若主从库的 runid 一致,说明从库断线前复制的就是当前主库,主库尝试执行增量同步操作
- 若主从库的 runid 不同,说明从库断线前复制的不是当前主库,主库对从库执行全量同步操作
环形缓冲区
避免从库断线重连主库后开始全量同步。
环形缓冲区(复制积压缓冲区 ,repl_backlog_buffer
)本质上是一个固定长度队列,可以避免数据移动。当缓冲满了,覆盖起始位置的旧数据。该缓冲区会在从库失联期间累计主库的写操作。
当从库重连,发送自己的复制偏移量到主库,主库会比较主从的复制偏移量
- 若从库偏移量还在缓冲区内,进行增量同步
- 若从库偏移量不在缓冲区内,进行全量同步
复制偏移量
主从库都维护各自的复制偏移量 offset,
- 主库向从库发送 n 字节的数据:offset = offset + n
- 从库接收主库发送 n 字节的数据:offset = offset + n
通过比较主从复制偏移量得知主从数据是否一致,偏移量相同则数据一致,否则数据不一致。
1.3、* 主从数据不一致
主从复制采用异步复制:Redis 主节点每次收到写命令后,异步发送给从节点
这样会出现主从数据不一致,具体来说
- 长连接命令传播阶段,主节点收到新的写命令,发送给从节点,主节点并不等待从节点实际执行完写命令后,再把结果返回给客户端,而是主节点在本地执行完命令后,向客户端返回结果。此时,若从节点还未执行完主节点同步过来的命令,则主从节点间数据短暂不一致。
- 主库宕机后,从库丢失主库一部分执行的命令,主从数据不一致。
若要求一致性,则规定只能读取主库而不是从库。
2、哨兵模式
2、哨兵模式
Sentinel 是 Redis 可用性的解决方案。Sentinel 通过 ping-pong
心跳检测的方法监控主从服务器,并在主服务器下线时自动对其实施故障转移,从而实现高可用。
客户端连接集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点的地址,然后再连接主节点。当主节点发生故障时,客户端会重新向 Sentinel 索要主节点地址,Sentinel l将最新的主节点地址告诉客户端。通过这样客户端无须重启即可自动完成节点切换。
Sentinel 节点个数是奇数,不存储数据,用来监控节点的状态和选举主节点,只提供一个数据节点服务。Sentinel 节点不仅监控 Redis 主从节点,同时还互相监控,形成多哨兵模式。
Sentinel 模式当中涉及的多个选举流程采用的是 raft 一致性算法。
sentinel 模式
2.1、Sentinel 职责
哨兵的职责有:
- 第一轮投票:判断主节点下线:主观下线 + 客观下线
- 第二轮投票:哨兵选主
- 由哨兵 leader 进行主从故障转移:从库选主 + 故障转移
判断节点故障
- 主观下线:适用于所有 Redis 节点 (主从节点 + Sentinel 节点)。Sentinel 以每秒 1 次的频率向所有 Redis 节点发送 ping 消息,然后通过接收目标节点的 pong 回复,来判断该节点是否下线。如果在规定时间内没有收到回复,则判定该节点主观下线。
- 客观下线:仅适用于主节点,当一个 Sentinel 节点判定一个主节点主观下线后,会向其他 Sentinel 节点询问对该主节点状态的判断。如果 Sentinel 节点超半数判定该主节点已下线,该判定该主节点客观下线。
针对主节点设计主观下线和客观下线两个状态,是因为主节点可能并没有故障,可能只是因为主节点系统压力大或者网络发生拥塞,导致主节点没有在规定时间内响应。因此,为了减少误判,需要多个哨兵节点组成集群共同判断主节点状态,防止误判主节点下线。
哨兵选主
- 哨兵选主 leader:所有 Sentinel 节点通过投票机制,按照谁发现谁处理的原则,选举 Sentinel Leader 节点,这需要 Sentinel 节点半数以上支持。
主从故障转移
由哨兵 leader 进行主从故障转移
- 从库选主: Sentinel leader 节点按照一定的规则在所有从节点中选择一个作为新的主节点。
- 故障转移:通知其他节点复制连接新的主节点,并将新主节点的信息发布订阅给客户端。若故障主节点重新上线,则作为新的主节点的从节点。
2.2、Sentinel 集群
哨兵以集群的方式部署,哨兵节点通过 Redis 的发布订阅机制,互相感知,互相连接,组成哨兵集群。同时,哨兵通过 INFO 命令,在主节点获取所有从节点的连接信息,和从节点建立连接,并进行监控。
2.3、Sentinel 缺点
- 部署时间长
- 没有解决数据丢失问题,异步复制
- 没有数据扩展机制
主从切换数据丢失
主从切换过程中,产生数据丢失的情况有两种
- 异步复制同步丢失:主从节点同步数据异步复制,若主节点还没来得及同步给从节点时发生宕机,主节点数据丢失。
- 集群产生脑裂数据丢失:主节点宕机后,主从数据不同步,重新选举产生新的主节点。旧主节点回来后降级为从节点,与新主节点进行同步时,从节点清空自己的缓冲区,丢失数据。
我们不能保证数据完全不丢失,只能做到尽量少的数据丢失。
3、Redis 集群
3.1、Cluster 原理
3.1.1、复制与高可用
Redis 集群与单机 Redis 一样,提供了主从复制功能。在 Redis 集群中,各个 Redis 服务器称为 Redis 节点,主节点负责处理客户端发送的读写命令,从节点负责对主节点进行复制。这是因为主从复制是异步复制,会造成主从节点不一致,因此使用主节点。同时,Redis 集群各个节点相互监视各自的运行状态,并在主节点下线时,通过提升该主节点的从节点为新主节点来继续提供服务。
3.1.2、分片与重分片
Redis 集群通过将数据库分散存储到多个节点上来平衡各个节点的负载压力,实现了 Redis 的分布式存储。要想做到主节点间数据的负载均衡,需要使用分布式一致性 hash。
分布式一致性 hash 算法: crc (key) % 16384
。Redis 集群将整个数据库空间划分为 16384 (214) 个哈希槽 slot 来实现数据分片,每个主节点负责处理其中的一部分槽。
这样做的好处是,固定算法,当集群扩容或缩容时,不会因为节点的增删,而导致路由失效。当增加节点时,集群就会将相应的槽以及槽中存储的数据迁移至新节点;当删除节点时,被移除的节点将自己负责的槽以及槽中数据转交给集群中的其他结点。更重要的是,整个分片 reshard 过程都可以在线进行,redis 集群不会因此停机。
3.2、Cluster 特征
- 去中心化,主节点关系对等。
- 解决了数据扩容
- 客户端与服务端缓存槽位信息,以服务端为准,客户端缓存主要为了避免连接切换
- 可人为数据迁移
- 主节点处理读写命令。
3.3、Cluster 流程
- 连接集群中任意一个节点
- 若数据不在该节点,回收到连接切换的命令,继而连接到目标节点
- 故障转移(主节点下移)
- 集群结点间互相监控,交换节点的状态信息
- 若某主节点下线,将会被其他主节点标记下线
- 接着从下线主节点的从节点中选择一个数据最新的从节点作为主节点
- 从节点继承下线主节点的槽位信息,并广播该消息给集群中的其他节点
缺点:主从异步复制在故障转移时仍存在数据丢失的问题
3.4、Cluster 管理
redis 管理集群提供两种方法:集群管理工具 redis-cli 和 redis 内置的集群管理命令
下面案例将要使用到的 redis-cli 命令如下:
redis-cli --cluster help # 创建集群 create <ip>:<port> --cluster-replicas <num> # 创建集群的同时,为每个主节点配备的从节点个数 # 查看集群的信息,群中任意节点地址作为参数,后面同理 info <ip>:<port> # 检查集群的配置 check <ip>:<port> # 重分片,将指定数量的槽从源节点迁移至目标节点,由目标节点负责迁移的槽和槽中数据 reshared <ip>:<port> --cluster-from # 源节点的ID --cluster-to # 目标节点的ID --cluster-slots <num> # 需要迁移的槽数量 # 添加节点,添加新节点 new 集群 existing,默认添加主节点 add-node <new_host>:<port> <existing_host>:<port> # 添加从节点,需要以下两个子命令 --cluster-slave --cluster-master-id <id> # 设置从节点要复制的主节点 # 移除节点 del-node <ip>:<port> <id>
3.4.1、搭建集群
设置启动配置文件
# 创建文件夹 mkdir -p 7001 7002 7003 7004 7005 7006 cd 7001 vi 7001.conf # 输入内容 pidfile "/home/jtz/redis-data/7001/7001.pid" logfile "/home/jtz/redis-data/7001/7001.log" dir /home/mark/redis-data/7001/ port 7001 daemonize yes cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 # 复制配置 cp 7001/7001.conf 7002/7002.conf cp 7001/7001.conf 7003/7003.conf cp 7001/7001.conf 7004/7004.conf cp 7001/7001.conf 7005/7005.conf cp 7001/7001.conf 7006/7006.conf # 修改配置 sed -i 's/7001/7002/g' 7002/7002.conf sed -i 's/7001/7003/g' 7003/7003.conf sed -i 's/7001/7004/g' 7004/7004.conf sed -i 's/7001/7005/g' 7005/7005.conf sed -i 's/7001/7006/g' 7006/7006.conf # 创建启动脚本 vi start.sh # 输入内容 #!/bin/bash redis-server 7001/7001.conf redis-server 7002/7002.conf redis-server 7003/7003.conf redis-server 7004/7004.conf redis-server 7005/7005.conf redis-server 7006/7006.conf # 创建启动脚本 vi stop.sh redis-cli -p 7001 shutdown redis-cli -p 7002 shutdown redis-cli -p 7003 shutdown redis-cli -p 7004 shutdown redis-cli -p 7005 shutdown redis-cli -p 7006 shutdown
创建集群
# 创建集群 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
搭建成功,查看集群配置信息
redis-cli --cluster check 127.0.0.1:7001 # 集群配置信息 M: 6d67700c3a40ea80488a48c232f9459e664bf899 127.0.0.1:7001 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: 9a66a88e4b4da0edeb2767d55dbd4623f0f2ec52 127.0.0.1:7004 slots: (0 slots) slave replicates 8b8339a497d07237b1fce6abef45d251132fc594 S: 3171812cc5b5e956c142321d6d31e68fa8708872 127.0.0.1:7006 slots: (0 slots) slave replicates e4da2884848793a8deb973fe623097d5089a8031 S: be0058062baca91b2a27f884c6cebd59f24f0cd2 127.0.0.1:7005 slots: (0 slots) slave replicates 6d67700c3a40ea80488a48c232f9459e664bf899 M: 8b8339a497d07237b1fce6abef45d251132fc594 127.0.0.1:7003 slots:[10923-16383] (5461 slots) master 1 additional replica(s) M: e4da2884848793a8deb973fe623097d5089a8031 127.0.0.1:7002 slots:[5461-10922] (5462 slots) master 1 additional replica(s)
测试集群
redis-cli -c -p 7001 127.0.0.1:7001> set name mark -> Redirected to slot [5798] located at 127.0.0.1:7002 # 分配到其他主节点存储 OK redis-cli -c -p 7003 127.0.0.1:7003> get name -> Redirected to slot [5798] located at 127.0.0.1:7002 # 重定向到存储该key的节点 "mark" 127.0.0.1:7002>
3.4.2、主从切换
# 主节点宕机,发生主从切换 redis-cli -p 7003 shutdown # 主节点重启 redis-server 7003/7003.conf # 配置信息变化 # 初始状态 M: 8b8339a497d07237b1fce6abef45d251132fc594 127.0.0.1:7003 slots:[10923-16383] (5461 slots) master 1 additional replica(s) # 主从切换后,从节点7004替换宕机的主节点7003 M: 9a66a88e4b4da0edeb2767d55dbd4623f0f2ec52 127.0.0.1:7004 slots:[10923-16383] (5461 slots) master 1 additional replica(s) replicates 6d67700c3a40ea80488a48c232f9459e664bf899 # 节点7003上线后,成为其他节点的从节点 S: 8b8339a497d07237b1fce6abef45d251132fc594 127.0.0.1:7003 slots: (0 slots) slave
3.4.3、主从扩容
先添加节点,再分配槽位
# 1、先添加节点 # 添加主节点,新添加的节点默认作为主节点 redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001 # 添加从节点 redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7001 --cluster-slave --cluster-master-id 6d67700c3a40ea80488a48c232f9459e664bf899 # 2、再分配槽 # 重分片,将主节点 7001 的 1000 个槽迁移至新的主节点 7007 redis-cli --cluster reshard 127.0.0.1:7001 --cluster-from 6d67700c3a40ea80488a48c232f9459e664bf899 --cluster-to 5043e35b99e3c3e45241dd7b748502aa8daf544c --cluster-slots 1000 # 对比迁移前后的集群配置 # 迁移前 M: 6d67700c3a40ea80488a48c232f9459e664bf899 127.0.0.1:7001 slots:[0-5460] (5461 slots) master 2 additional replica(s) M: 5043e35b99e3c3e45241dd7b748502aa8daf544c 127.0.0.1:7007 slots: (0 slots) master # 迁移后 M: 6d67700c3a40ea80488a48c232f9459e664bf899 127.0.0.1:7001 slots:[1000-5460] (4461 slots) master 1 additional replica(s) M: 5043e35b99e3c3e45241dd7b748502aa8daf544c 127.0.0.1:7007 slots:[0-999] (1000 slots) master 1 additional replica(s)
3.4.4、主从缩容
先移动槽位,再删除节点
# 1、先移动槽位 # 将主节点 7007 的所有槽迁移至主节点 7001 redis-cli --cluster reshard 127.0.0.1:7001 --cluster-from 5043e35b99e3c3e45241dd7b748502aa8daf544c --cluster-to 6d67700c3a40ea80488a48c232f9459e664bf899 --cluster-slots 1000 # 迁移后的集群配置 M: 6d67700c3a40ea80488a48c232f9459e664bf899 127.0.0.1:7001 slots:[0-5460] (5461 slots) master 2 additional replica(s) M: 5043e35b99e3c3e45241dd7b748502aa8daf544c 127.0.0.1:7007 slots: (0 slots) master # 2、删除节点 # 删除节点 7007 redis-cli --cluster del-node 127.0.0.1:7001 5043e35b99e3c3e45241dd7b748502aa8daf544c # 此时7008成为其他节点的从节点 M: 6d67700c3a40ea80488a48c232f9459e664bf899 127.0.0.1:7001 slots:[0-5460] (5461 slots) master 2 additional replica(s) S: 1d5f5827cdb9f294de702f4247d1471fce9545df 127.0.0.1:7008 slots: (0 slots) slave replicates 6d67700c3a40ea80488a48c232f9459e664bf89 # 删除从节点7008 redis-cli --cluster del-node 127.0.0.1:7001 1d5f5827cdb9f294de702f4247d1471fce9545df