文章目录
宏观角度回顾一下Redis实现高可用相关的技术。它们包括:持久化、复制、哨兵和集群
- 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
- 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
- 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
- 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
持久化
RDB方式
对性能的影响相对较小
将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。所以会存在很多个文件
save
//阻塞保存
bgsave
//后台不阻塞保存
save m n
//当m秒内发生n次变化时,会触发bgsave
主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令
执行shutdown命令时,自动执行rdb持久化
AOF方式
实时;文件大、恢复速度慢、对性能影响大
将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍
Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在配置文件中配置:appendonly yes
因此AOF不需要触发,记录Redis的每条写命令
主从
单机版的Redis属于保证CP(Consistency & Partition-Tolerancy)而牺牲A(Availability)
主从模式是AP
主节点和多个从节点。提供了冗余和故障转移能力,适合一些读多写少的应用场景(主写从读)。
主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情。
从节点开启主从复制,有3种方式:
- 配置文件
在从服务器的配置文件中加入:slaveof <masterip> <masterport>
- 启动命令
redis-server启动命令后加入--slaveof <masterip> <masterport>
- 客户端命令
Redis服务器启动后,直接通过客户端执行命令:slaveof <masterip> <masterport>
,则该Redis实例成为从节点。
通过slaveof no one断开。需要注意的是,从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。
全部复制
- 场景
用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。 - 同步流程
- 主节点 bgsave生成当前RDB文件 + 复制缓冲区记录后续命令
- RDB文件 发给从节点 >> 从节点清空老数据、载入新RDB文件
- 复制缓冲区命令 发给从节点 >> 从节点执行这些写命令
部分复制
Redis2.8开始提供部分复制,
从节点发送psync命令请求同步数据,此时根据主从节点当前状态的不同,选择同步方式是全量复制或部分复制
-
重要概念
- 复制偏移量(offset)
主节点和从节点分别维护一个:记录节点 传递/接收 的字节数
判断主从节点的数据库状态是否一致,差多少数据没同步 - 复制积压缓冲区
先进先出(FIFO)队列 默认大小1MB (建议根据 平均中断时间*每秒产生命令 进行调整)
记录主节点最新执行命令,超过队列大小的会被挤出缓冲区 - 服务器运行ID(runid) :
识别每个节点的随机ID(每次启动都不一样)
从节点会将主节点的runid存起来,断开重连的时候判断是否同步过
- 复制偏移量(offset)
-
场景
用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。 -
使用条件
主节点的runid没变,且 offset之后的数据 在复制积压缓冲区中
哨兵
Redis 2.8版本开始引入。哨兵的核心功能是主节点的自动故障转移
-
哨兵节点:
哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。 -
数据节点:
主节点和从节点都是数据节点。
哨兵系统部署
哨兵系统中节点分为 数据节点 与 哨兵节点
启动数据节点
哨兵系统中的主从节点,与普通的主从节点配置是一样的
启动哨兵节点
- 部署建议
哨兵节点的数量应不止一个,一方面增加哨兵节点的冗余,避免哨兵本身成为高可用的瓶颈;另一方面减少对下线的误判。此外,这些不同的哨兵节点应部署在不同的物理机上。
哨兵节点的数量是奇数
哨兵节点的配置应一致,包括硬件、参数、时间
- 配置sentinel.conf
//至少需要 N个哨兵节点同意,才能判定主节点故障并进行故障转移。
sentinel monitor 主节点名称 主节点ip 主节点端口 N
//一个哨兵可以监控多个主节点,通过配置多条sentinel monitor即可实现
sentinel monitor ...
sentinel monitor ...
- 启动 redis-sentinel
# 启动有两种方式,二者作用是完全相同的
redis-sentinel sentinel.conf
redis-server sentinel.conf --sentinel
- 故障转移
在故障转移阶段,哨兵和主从节点的配置文件都会被改写。
- 对于主从节点,主要是slaveof配置的变化:新的主节点没有了slaveof配置,其从节点则slaveof新的主节点。
- 对于哨兵节点,除了主从节点信息的变化,纪元(epoch)也会变化,下图中可以看到纪元相关的参数都+1了。
哨兵的原理
-
定时任务:
每个哨兵节点维护了3个定时任务。定时任务的功能分别如下:
通过向主从节点发送info命令 获取最新的主从结构;
通过发布订阅功能 获取其他哨兵节点的信息;
通过向其他节点发送ping命令进行 心跳检测,判断是否下线。 -
主观下线:
在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。 -
客观下线(主节点):
哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr
命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对主节点进行客观下线。
只实现了主节点的故障转移;从节点故障时只会被下线,不会进行故障转移 -
选举领导者哨兵节点
主节点被判断客观下线以后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。 -
故障转移
- 在从节点中选择新的主节点:
slave-priority优先级 > 复制偏移量最大 > runid最小 - 更新主从状态:
通过slaveof no one命令,让选出来的从节点成为主节点;
通过slaveof命令让其他节点成为其从节点 - 将已经下线的主节点设置为新的主节点的从节点
当重新上线后,它会成为新的主节点的从节点
- 在从节点中选择新的主节点:
集群
Redis 3.0开始引入的。提供高可用性、高性能和可扩展性。
通过分片( sharding ) 将数据均匀地分布在多个节点上,
允许在增加或减少节点时进行水平扩展
并提供复制和故障转移。
redis-cluster
- redis-cluster 启动节点
- 节点握手,形成集群网络
- 集群数据分区分配 哈希槽【hash slot】
- 【可选】指定主节点副本 cluster replicate
- 集群节点
在集群中,没有数据节点与非数据节点之分:所有的节点都存储数据,也都参与集群状态的维护。
不过节点会开2个端口。- 普通端口
用于为客户端提供服务,节点数据迁移 - 集群端口
节点之间的通信:增减节点、故障转移等操作
- 普通端口
数据分区
将数据自动切分(split)到多个节点的能力。
哈希槽【hash slot】=虚拟节点
-
概念
虚拟节点,数据hash不直接放在实际节点,
变成:数据hash >> 槽 >> 实际节点 -
hash作用:
扩展写数据能力,让多个节点能承担写数据 -
槽作用:
解耦了数据和实际节点之间的关系,增加或删除节点对系统的影响变小- 一致性hash:一个实际节点下线,相邻节点要独自承载它的全部数据
- 哈希槽:一个实际节点下线,把它的槽均匀分配给其他节点
计算哈希值使用的算法是CRC16
图片修改自:https://blog.youkuaiyun.com/yejingtao703/article/details/78484151
读数据限制
由于数据分布在不同的节点中,客户端通过某节点访问数据时,数据可能不在该节点中
Dummy客户端
redis-cli -c
请求key- 计算key属于哪个槽:CRC16(key) & 16383
- 槽是否在当前节点
- 在:执行命令
- 不在:查询正确节点,返回错误
- 客户端请求正确节点
Smart客户端
- Java的JedisCluster 初始化 cluster slots。得到slot->node的缓存
- JedisCluster为每个节点创建连接池(即JedisPool)
- 根据key->slot->node选择需要连接的节点
- 失败:随机重试 + 重新获取 cluster slots
集群其他限制
批量操作受限
mget、mset操作,只有当操作的key都位于一个槽时,才能进行
keys/flushall等操作
keys/flushall 结果只针对当前节点
事务/Lua脚本:
事务及Lua脚本,所涉及的key必须在同一个节点。
db0数据库
单机Redis节点可以支持16个数据库,集群模式下只支持一个,即db0。
复制结构
只支持一层复制结构,不支持嵌套。
Hash Tag:解决部分限制
Hash Tag原理是:让不同的key拥有相同的hash值,从而分配在同一个槽里
具体:当一个key包含 {Hash Tag}
的时候,不对整个key做hash,而仅对 {Hash Tag} 包括的字符串做hash。
高可用
集群中的主从
为了集群节点的高可用,每个集群节点都是由一组主从节点组成
故障转移
集群中没有独立的哨兵节点。每个Node互相监听
通过定时任务发送PING消息检测其他节点状态;节点下线分为主观下线和客观下线;客观下线后选取从节点进行故障转移。
- 心跳检测:每个节点的master 定期的向其他节点 master 发送 ping命令,
- 如果没有收到pong 响应,则会认为主观下线,并将这个消息发送给其他的 master。
- 其他的 master 在接收到这个消息后就保存起来。当某个节点的 master 收到 半数以上的消息认为这个节点主观下线后,就会判定这个节点客观下线。
- 故障转移:这个客观节点下线后,其他的 master 节点 就会选举 下线的master中的 slave 一个变成 新的master 继续工作
一致性
不保证数据的强一致性
- 原因
- 主从异步复制延迟
- 集群出现网络分裂(network partition), 并且一个客户端与至少包括一个主节点在内的少数(minority)实例被孤立
例如:
一个集群3个节点,各有一个从节点 :A (A1)、 B(B1) 、 C(C1)
A (A1)、 B(B1) 、(C1) 与 C 的网络中断。
C还在接收客户端写请求,C1被转移为主节点。
伸缩:槽均匀迁移
伸缩的核心是槽迁移:修改槽与节点的对应关系,实现槽(即数据)在节点之间的移动