0、引言
单机式Redis存在以下问题,因此需要Redis集群化来解决这些问题
1、持久化
1.1 RDB(Redis Database Backup file )持久化
Redis数据快照,简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
(1)执行方法:
(2)原理
着重比较一下save命令与bgsave:
save会阻塞所有命令,因为备份的过程不允许更改。
而bgsave不会阻塞其他命令、是通过fork一个子线程共享数据,然后读取内存数据并写入 RDB 文件,如果主线程此时进行写操作,则会拷贝一份数据出来执行写操作。
个人理解,这个Redis的RDB文件类似于Mysql里的Redo日志,存的是数据。
1.2 AOF(Append Only File )持久化
(1)AOF持久化原理
与RDB相对的,AOF并不是直接将数据存入磁盘进行持久化保存,
而是通过将每一个写命令记录在AOF文件中实现持久化。个人理解,就很类似于Mysql的Binlog日志,记录的是具体的语句操作。
例如,set num 123这一步操作,AOF就会记录相应的操作语句。
其刷盘频率的规定如下: 通过redis.conf文件进行配置:
# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no
(2)AOF的文件重写
既然记录的是写操作,如果对一个数据多次重复写,那么有很多次写操作都是无用操作,因此AOF能够执行如图所示重写功能:
1.3 RDB与AOF对比总结
2、Redis主从架构
2.1 简单搭建
类似于MySQL,单节点Redis并发能力有限,需要搭建主从结构:即,主Redis进行写操作,从Redis执行读操作,并且主从之间要保持数据同步。
搭建流程:
连接到从redis:
redis-cli -p 端口号
建立其master:
SLAVEOF 主IP地址 主端口号(SLAVEOF也可以换成REPLICAOF)
2.2 全量同步
(1)基本流程
主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点。
在同步的过程中,需要用到上文提到的RDB文件。
还会用到repl_baklog文件:通过bgsave生成RDB时,是子进程复制出的数据进行存盘,但如果此时主线程趁此时进行写数据,那这些新的数据不就没有同步进slave里了吗??
因此,redis会在此时通过repl_baklog记录bgsave期间的所有命令,再发给slave进行补充,保证数据的完整。
具体流程如下:
(2)问题:slave向master请求数据同步,master如何知道这是第一次来连接?
通过以下两个概念作为判断依据:
Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
此为关键:这个id如果不同,说明是第一次!offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。
master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。
2.3 增量同步
在2.2节的基础上,当主从的Replication Id相同时,我们可以理解为,此时slave数据集至少是master的子集了。
主从数据剩余部分具体相差多少呢?可以通过offset偏移量来进行记录。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset。每次在第二阶段,master就去发送:从 已经拷贝过的offset开始 到 当前offset的数据,如图所示:
----> ---->
repl_baklog这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖,但因为slave(绿色部分已经同步了,因此master可以直接覆盖也无妨)
但是如果slave宕机过久,导致master把尚未同步的红色部分覆盖了(即,红色超过了一整圈):例如下图,此时只能全量同步了。
2.4 知识总结
简述全量同步和增量同步区别?
全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
slave节点第一次连接master节点时
slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
slave节点断开又恢复,并且在repl_baklog中能找到offset时
3、 Redis哨兵
在第2节中,我们讲述了从机宕机后,通过repl_baklog的offset来进行恢复从机的数据。
但如果,宕机是主机而不是从机,该怎么办?显然这时候需要指定新的主机了!而redis哨兵机制能够自动监控主机的健康状态、以及在主机挂掉后指定从机。
3.1 原理
监控:Sentinel 会不断检查您的master和slave是否按预期工作
这里sentinel会反复向主机发送ping,如果在规定时间内收到pong,说明主机还是好的,反之反之。如果有超过半数的哨兵认为主机挂了,那说明主机挂了。自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
具体怎么选新的主机呢?
首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
最后是判断slave节点的运行id大小,越小优先级越高通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
3.2 故障转移步骤
故障转移步骤有哪些?
首先选定一个slave作为新的master,执行slaveof no one
然后让所有节点都执行slaveof 新master
修改故障节点配置,添加slaveof 新master
4、 Cluster分片集群
对于第3节中的哨兵模式,其每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。当有海量数据需要存储、高并发的写问题出现时候,就要采取分片集群的方式来解决问题了。
4.1 分片集群的特征
集群中有多个master,每个master保存不同数据
每个master都可以有多个slave节点
master之间通过ping监测彼此健康状态(代替哨兵),如果挂掉了会故障转移。
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
4.2 散列插槽
插槽算法把整个数据库被分为16384个slot(槽),每个进入Redis的键值对,根据key进行散列,分配到这16384插槽中的一个。使用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都可以处理这16384个槽。
集群中的每个节点负责一部分的hash槽,比如当前集群有A、B、C个节点,每个节点上的哈希槽数 =16384/3,那么就有:
节点A负责0~5460号哈希槽
节点B负责5461~10922号哈希槽
节点C负责10923~16383号哈希槽
即: 数据key不是与节点绑定,而是与插槽绑定。 这样,三个节点分别处理不同的槽位置的数据,在存、取数据时,会根据运算得到的槽,自动切换节点。
4.3 动态扩容、缩容
在4.2节的基础上,我们知道,实际上数据不是存储在节点上的,而是插槽上的。因此,如果想增加新的节点时,给新的节点分配插槽即可。如果想删掉旧的节点时,先把插槽转移给别的节点,再把没有插槽的空节点删了即可。
(1)动态扩容举例
使用redis-cli的add-node命令新增一个主节点8007(master),前面的ip:port为新增节点,后面的ip:port为集群中已存在的节点。
src/redis-cli --cluster add-node 192.168.100.100:8007 192.168.100.100:8001
当添加节点成功以后,新增的节点不会有任何数据,因为它还没有分配任何的slot(hash槽),我们需要为新节点手工分配hash槽。
使用redis-cli的rehash命令为8007分配hash槽,找到集群中的任意一个主节点,对其进行重新分片工作。
src/redis-cli --cluster reshard 192.168.100.100:8001
How many slots do you want to move (from 1 to 16384)? 600
(ps:需要多少个槽移动到新的节点上,自己设置,比如600个hash槽)
What is the receiving node ID? 2728a594a0498e98e4b83a537e19f9a0a3790f38
(ps:把这600个hash槽移动到哪个节点上去,需要指定节点id)
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node 1:all
(ps:输入all为从所有主节点(8001,8002,8003)中分别抽取相应的槽数指定到新节点中,抽取的总槽数为600个)
... ...
Do you want to proceed with the proposed reshard plan (yes/no)? yes
(ps:输入yes确认开始执行分片任务)
槽位迁移后,对应槽位中的数据也会迁移!
(2)动态缩容举例
例如,我们将(1)中的8007节点删除:
因为主节点8007的里面是有分配了hash槽的,所以我们这里必须先把8007里的hash槽放入到其他的可用主节点中去,然后再进行移除节点操作,不然会出现数据丢失问题。
src/redis-cli --cluster reshard 192.168.100.100:8007
... ...
How many slots do you want to move (from 1 to 16384)? 600
What is the receiving node ID? baf0c2f3afde2410e34351a8261a703f1394cee9
(ps:这里是需要把数据移动到哪?8001的主节点id)
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node 1:4b339ad25b4884c2ff6de8a8ec2bc8766f8faf0b
(ps:这里是需要数据源,也就是我们的8007节点id)
Source node 2:done
(ps:这里直接输入done 开始生成迁移计划)
... ...
Do you want to proceed with the proposed reshard plan (yes/no)? Yes
(ps:这里输入yes开始迁移)
至此,我们已经成功的把8007主节点的数据迁移到8001上去了,我们可以看一下现在的集群状态如下图,你会发现8007下面已经没有任何hash槽了,证明迁移成功!
最后我们直接使用del-node命令删除8007主节点即可
src/redis-cli --cluster del-node 192.168.100.100:8007 4b339ad25b4884c2ff6de8a8ec2bc8766f8faf0b