Redis数据结构与对象:https://blog.csdn.net/qq_41822345/article/details/130456081
Redis单机数据库:https://blog.csdn.net/qq_41822345/article/details/130909789
先总结一下Redis的四种部署模式(除了第一种是单机数据库,其它都是多机数据库):
1、单机模式
概念:Redis单机模式是最简单的部署模式,Redis将数据存储在单个节点上。
优点
- 简单易用。架构简单,部署方便。
- 低延迟高性能。毕竟是单机,少了很多的网络传输。
缺点
- 无法保证数据的可靠性。
- 处理能力有限。受限于单核CPU的处理能力。
- 内存容量有限 。因此一般不会用于生产环境。
2、主从复制模式
概念:主从模式是指可以让一个服务器(slave)去复制另一个服务器(master)的数据。
优点
- 数据备份。Master能自动将数据同步到Slave,且同步是以非阻塞bgsave的方式进行的。
- 读写分离。 分担Master的读压力。
缺点
- 不具备自动容错与恢复功能。
- 有数据不一致的风险。比如master宕机。
- 难以支持在线扩容,Redis的容量仍然受限于单机配置
3、哨兵模式
概念: 哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。
优点
- 主从复制模式有的优点,哨兵模式也有。
- 系统可用性更高。master挂掉可以自动进行切换。
缺点
- 主从复制模式有的优点,哨兵模式也有。
- 需要额外的资源来启动sentinel进程,实现相对复杂一点。
4、集群模式
概念:集群模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题。
优点
- 无中心架构。
- 可维护性。降低运维成本,提高系统的扩展性和可用性。
- 高可扩展。可线性扩展到1000多个节点,节点可动态添加或删除。
- 高可用性。部分节点不可用时,集群仍可用。
缺点
- 架构复杂性。客户端实现的复杂性。开发难度提高。
- 数据通过异步复制,不保证数据的强一致性。
- slave节点只能作为数据备份,不能缓解读压力。
- 不支持多数据库,只有1个数据库空间db0。
- 运维复杂,需要面临更多问题:比如Key事务操作、big-key/hot-key的处理更加复杂。
主从模式是指通过执行 slaveof 命令或设置 slaveof 选项,让一个服务器(slave)去复制另一个服务器(master)的数据。
只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
主从复制是哨兵和集群能够实施的基础,因此可以说主从复制是Redis高可用的基石。
Redis的复制功能分为 同步 和 命令传播 两个操作。
一个redis实例变成另一个redis实例的从节点有三种方式。
当redis实例通过上述方式变成一个从节点后,首先要执行同步操作——通过向主服务器发送SYNC命令来完成,步骤如下:
sync
命令;bgsave
命令,后台生成RDB文件的同时,并使用缓冲区记录之后的所有写命令;bgsave
命令时的数据库状态;当主服务器执行新的写命令时,主从服务器就可能出现不一致状态。这时主服务器需要对从服务器执行命令传播操作:将写命令传播给从服务器,这也是从服务器唯一能接收写命令的方式。
以上复制方式在从节点初次复制主节点时,没有什么问题,但是当断线后重复制时,从节点需要再次发送sync
命令,相当于初次复制。这种为了让从服务器补足一小部分缺失的数据,但却要把所有数据重新复制一遍的做法是非常低效的。
SYNC
命令是一个非常耗费资源的操作【非必要时不要执行这个命令】
- 主服务器需要执行 bgsave 命令,这需要耗费主服务器大量的CPU、内存和I/O资源。
- 主服务器将RDB文件发送给从服务器,这占用主服务器和从服务器大量的网络资源。
- 从服务器在载入RDB文件的期间,会因为阻塞无法处理其它命令请求。
从Redis2.8版本开始,使用
PSYNC
代替SYNC
命令,它有两种模式:完整同步模式和部分同步模式。完整同步模式用于处理复制,等同于旧版复制功能。
部分同步模式解决了旧版复制功能在处理断线后重复制时出现的低效问题。
部分同步模式只需要将从服务器缺少的写命令发送给从服务器执行就可以了。
部分同步功能的实现基于以下三个部分构成:
复制偏移量offset
复制积压缓冲区
服务器的运行ID
实现原理简述:从服务器发生断连时,它会向主服务器发送PSYNC 主服务器运行ID 复制偏移量offset
请求进行部分同步。主服务器接收到从服务器的PSYNC
命令之后,首先对比从服务器传来的 主服务器运行ID ,如果和自己一致,那就检测从复制偏移量offset之后的数据是否存在于复制积压缓冲区,如果存在,则响应 +CONTINUE
回复给从服务器,表示可以进行部分同步操作。
1、连接建立阶段【主从服务器状态属性中互相保存IP:port的过程】
step1、保存主节点信息
从服务器节点执行完slave of命令之后,会将主服务器的IP:port保存到从服务器节点状态中的masterhost属性和masterport属性,然后返回OK给客户端。
step2、建立socket连接
从服务器根据step1中保存的IP:port,创建连向主服务器的socket。主服务器在accept从服务器的连接之后,也会创建相应的socket。至此,主从服务器将基于此socket对进行通信。
step3、发送ping命令
从服务器向主服务器发出ping进行连接测试,如果收到响应pong说明连接测试成功。否则断开连接并重连master。
step4、身份验证
如果需要认证【主从双方都配置了密码】,则进行认证成功之后才能进行下一步。
step5、发送从节点端口信息
从服务器执行命令 replconf litsten-port port
向主服务器发送端口。主服务状态中属性中保存从服务器的IP:port。
2、数据同步阶段
psync
命令。
psync
命令,及当前服务器状态,决定执行全量复制还是部分复制。
sync
命令执行全量复制;+CONTINUE
,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;+FULLRESYNC
,表示要进行全量复制,其中runid表示主节点当前的runid,offset表示主节点当前的offset,从节点保存这两个值,以备使用。3、命令传播阶段
PING
和 REPLCONF ACK
。主服务器通过向从服务器传播命令来更新从服务器的状态,保持主从服务器一致,同时从服务器也需要通过向主服务器发送 replconf ack
命令(每秒一次)来进行心跳检测。
心跳检测有三个作用:
1、检测主从服务器的网络连接状态
主服务器如果有超过1秒的时间没有收到来自从服务器的心跳检测命令 replconf ack
,那么主服务器就知道主从服务器之间的连接有问题了。通过 info replication
命令可以看到从服务器最后一次向主服务器的心跳检测过了多少秒。一般延迟(lag)值在0到1秒之间属于正常。
2、辅助实现min-slaves配置选项
主服务器一般会配有设置 min-slaves-to-write
和 min-slaves-max-lag
参数。表示如果slave少于 min-slaves-to-write
个 或者有 min-slaves-to-write
个slave的延迟(lag)都不小于 min-slaves-max-lag
秒,则主服务器拒绝写命令。
3、检测命令丢失
从服务器会在发送 replconf ack
命令中告诉主服务器自己的复制偏移量offset,主服务器如果发现偏移量比自己少,就知道有命令发生的丢失,这时主服务器会从自己的复制积压区找到从服务器缺少的数据,重新发送给从服务器。(补发缺失数据 与 部分重同步原理一样【都是Redis 2.8版本新增的功能】,它们的区别是前者未发生断连,只是丢失了某些命令,而后者属于发生了断线并重连)
哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。
由一个或多个Sentinel去监听(并且Sentinel也可以互相监视)任意多个主服务以及主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线的主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已经下线的主服务器继续处理命令请求。
Sentinel 本质上只是一个运行在特殊模式下的Redis服务器。
Sentinel可以监控任意多个Master和该Master下的Slaves( 即多个主从模式) 。
在同一个Sentinel哨兵下的,不同主从模型彼此之间相互独立,
Sentinel的三个任务
监控
Sentinel 会不断地检查主服务器和从服务器是否运作正常。
提醒
当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移
当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作,将其中一个从服务器升级为新的主服务器。
Sentinel网络
Redis在哨兵模式下,在监控主从服务器之间,需要先完成Sentinel节点的初始化。流程如下:
step1、启动并初始化Sentinel。
执行 redis-sentinel /path/to/your/sentinel.conf
命令 或者 redis-server /path/to/your/sentinel.conf --sentinel
命令
a、使用Sentinel专用代码。
Sentinel模式下Redis服务器只有以下功能:
b、初始化Sentinel状态,即初始化sentinalState结构
c、初始化Sentinel状态中的Master属性
d、创建连向主服务器的网络连接
step2、获取主服务器信息(每10秒一次)
step3、获取从服务器信息(每10秒一次)
step4、向主/从服务器发送信息(每2秒一次)
step5、接收来自主服务器和从服务器的频道信息
sentinel与主从服务器之间需要创建两种连接,一个是命令连接,一个是订阅连接。
sentinel需要通过命令连接向主从服务器发送信息,同时也需要通过订阅连接从主从服务器那接收信息。
通过交换信息,来进行下面的步骤。
step6、更新Sentinel字典
step7、创建连向其它Sentinel的命令连接
Sentinel模式下的服务器状态如图:
Sentinel模式下,Sentinel初始化完成之后,在Sentinel网络中对所有的Matser和Slave进行监控。如果这时出现主服务器故障,则走如下流程。
step1、判定主观下线(每1秒一次)
Sentinel会向所有与它创建了命令连接实例(包括主服务器、从服务器、其它Sentinel)发送Ping
命令。并根据有效回复 Pong
、-Loading
、-Matserdown
(其它回复均为无效回复)进行检测。如果超过 down-after-milliseconds
毫秒都没有有效回复,Sentinel就会把matser标记为主观下线。
down-after-milliseconds
也是Sentinel判断master属下的slave,以及监视该matser的其它Sentinel是否进入主观下线的标准。
step2、判定客观下线
当Sentinel检测到主观下线后,就会询问其它监视该master的Sentinel,当超过一定数量的Sentinel都认为该Master已经下线后,就会将主服务器判定为客观下线。
is-matser-down-by-addr
命令,对其它Sentinel进行询问is-matser-down-by-addr
命令,分析并检查master是否下线,并做出回复。step3、选举Sentinel Leader【基于Raft协议】
当检测出客观下线后,所有监视这个下线master的Sentinel需要先进行leader选举,由领导Sentinel进行故障转移操作。
step4、选举新的主服务器。这是由领头Sentinel会在所有Slave中选出新的Master,选举规则如下:
down-after-milliseconds * 10
毫秒的Slave。step5、让其余所有Slave服务器复制新的Master服务器。
step6、当下线的Master重新上线后,将它变成新的Master服务器的Slave。
Redis集群是由Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。
- RedisCluster 是 Redis 的亲儿子,它是 Redis 作者自己提供的 Redis 集群化方案。
- redis在3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的数据。cluster模式为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。
- Redis Cluster是一种服务器Sharding技术(分片和路由都是在服务端实现),采用多主多从,每一个分区都是由一个Redis主机和多个从机组成,片区和片区之间是相互平行的。
相关参考:
Redis Cluster数据分片实现原理、及请求路由实现:https://blog.csdn.net/Seky_fei/article/details/107611850
Redis集群 - 图解 - 秒懂(史上最全):https://www.cnblogs.com/crazymakercircle/p/14698576.html#autoid-h3-7-0-0
- Redis 集群方案主要有3类:
- 1、基于官方的 Redis cluster 的服务端分片方案。(分片和路由都是在服务端实现)
- 2、使用类 codis 的代理模式架构,按组划分,实例之间互相独立。(分片和路由在代理实现)
- 3、代理模式和服务端分片相结合的模式。
集群cluster、节点node、槽slot、键key之间的关系
cluster:node = 1:n
node:slot = 1:n
slot:key = 1:n
什么是槽slot?
Redis Cluster是Redis3.0引入的一种无中心化的集群,客户端可以向任何一个节点通信,Redis Cluster将数据的key通过将CRC16算法的结果取模16383后,分给16384个slot槽,集群的每个节点负责一部分hash槽,节点只负责管理映射到这个槽的KV数据,对于不是当前槽的KV数据,会向客户端发送一个MOVED,表示需要客户端重新重定向到其它节点。
为什么引入槽slot?
为什么没有使用一致性hash算法,而是使用了哈希槽预分片?
为什么是16384个槽位(2^14)?
集群创建流程就是一个创建集群实例数据结构体(一定都会有的结构体:redisServer、redisClient;集群模式下才有数据:clusterNode、clutserLink、clutserState等)的过程。
Redis服务器在启动时会根据cluster-enabled
配置选项是否为yes来决定是否开启服务器的集群模式。
Redis节点在集群模式下会继续使用所有在单机模式中使用的服务器组件:
serverCron
函数(这时该函数也执行在集群模式下需要执行的操作:发送Gossip消息、下线检测、故障转移等)初始化集群模式下数据结构:
cluster meet命令
该命令用于将一个节点加入到集群中。这个过程类似三次握手,先由节点A向节点B发送meet消息;节点B返回pong消息给节点A,这是对meet消息的确认;最后节点A还会发送一次ping消息给节点B,告诉节点B收到了B的pong消息,至此握手完成。
Gossip协议
通过Gossip协议传播新节点的加入,让其它节点也与新节点进行握手。最终所有节点都会认识新节点。
Redis集群的整个数据库被分为16384个槽slot,每个slot都必须分配到某个节点上,否则集群将处于下线状态。
节点的clusterNode结构记录了节点的槽指派信息。
- slots属性记录了节点负责处理槽信息。numslots属性记录了当前节点负责处理的槽数量
- slots是一个二进制位数组。长度为16384/8=2048,每个slots[i]有8位二进制,代表了16384个槽。
传播节点的槽指派信息
每个节点都会将自己的slots数组通过消息发送给集群中的其它节点。这样集群中的每个节点都会知道所有的槽指派信息。
记录集群的槽指派信息
集群的槽指派信息当然是记录在clutserState.slots中,它的每个数据项都是一个指向clusterNode结构的指针。
- clutserNode.slots数组只记录了当前节点的槽指派信息。
- clutserState.slots数组记录了全部节点的槽指派信息。
cluster addslots命令
通过该命令将槽指派给执行该命令的节点。
当客户端向节点发送与数据库键key有关的命令时,需要经过以下步骤:
step1:计算键属于哪个槽
CRC16(key)& 16383
step2:判断槽是否由当前节点负责处理
根据step1计算出一个值 i 之后,判断clutserState.slots[i] = clutserState.myself
step3:如果clutserState.slots[i] != clutserState.myself。则根据clutserState.slots[i]所指向的clutserNode结构,获取IP:port,通过moved错误返回给客户端,从而转向负责处理槽slots[i]的节点。
当节点发现键 key 所在的槽 slot[i] 并非由自己负责处理的时候,节点就会返回给客户端一个moved错误(包含了正确的负责处理槽 slot[i] 的节点),从而指引客户端专项正在负责槽的节点。
客户端会先根据moved错误提供的IP地址和端口来连接节点,然后再进行转向。
clutserState.slots_to_keys是一个跳跃表,它保存了槽与键之间的关系。通过记录各个数据库键所属的槽,节点可以很方便的对属于某个或某些槽的所有数据库键进行批量操作。比如重新分片。
Redis集群的重新分片操作是由管理组件redis-trib
负责执行的,它通过向源节点和目标节点发送命令来进行重新分片操作步骤以及执行的命令如下:
step1:开始对槽slot进行重新分片操作
step2:通知目标节点准备导入槽slot的键值对;cluster setslot slot_id importing source_id
step3:通知源节点准备迁移槽slot的键值对;cluster setslot slot_id migrating target_id
step4:向源节点获取最多count个属于槽slot的键值对;cluster getkeyinslot slot_id count
step5:将step4获取的每个键迁移到目标节点(通过pipeline 机制批量迁移);migrate target_id target_port kets
step6:重复执行step4和step5,直到属于槽slot的所有keys都被迁移完毕。
step7:传播槽指派信息;任意发送cluster setslot slot_id node target_id
命令给某个节点,最终会通过Gossip协议传播给整个集群。
如果重新分片涉及到多个槽slot,那么redis-trib
对于每个槽分别执行上面的操作。
moved错误和ask错误的区别?
区别在于槽slot[i]目前由哪个节点负责。
- 对于moved错误,说明slot[i]由其它节点负责,之后对于slot[i]的操作会直接发送到负责它的节点。
- 对于ask错误,说明slot[i]正在进行重分片,slot[i]的负责节点还在迁移中。
当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息;客户端再向正确的节点发送命令时,如果此时正在进行集群扩展或者缩空操作,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制。
Redis集群中的每个node(节点)负责分摊这16384个slot中的一部分,也就是说,每个slot都对应一个node负责处理。当动态添加或减少node节点时,只需要将16384个槽做个再分配,将槽中的键值和对应的数据迁移到对应的节点上。
redis cluster提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容,也可以下线部分节点进行缩容。可以说,槽是 Redis 集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。
集群的伸缩流程原理基于重新分片原理。
a、集群扩容
当一个 Redis 新节点运行并加入现有集群后,我们需要为其迁移槽和槽对应的数据。首先要为新节点指定槽的迁移计划,会确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据均匀。
b、集群收缩
Redis集群的节点分为Master和Slave,其中Master负责处理槽slot,而从节点slave用于复制master(一个节点成为从节点,并开始复制某个主节点的这一信息会通过消息发送给集群中的其它节点,最终集群中的所有节点都会知道这一信息),并可以在master下线时,代替下线主节点成为新主节点继续处理命令请求。
step1、故障检测
step2、故障转移
slave of no one
成为新的主节点。Redis集群中的各个节点通过发送和接收消息来进行通信。节点发送的消息主要有以下五种:
- Redis集群中各个节点通过
Gossip
协议来交换自己知道的节点状态消息。Gossip协议的实现由meet、ping、pong三种消息实现,它们都由clusterMsgDataGossip
结构组成。- 每次发送meet、ping、pong消息时,发送者都会从自己的已知节点列表中随机选出两个节点的信息(包括节点名称、ip和端口等)保存到要发送的消息结构体clusterMsgDataGossip结构里面。
在集群的节点数量比较大的情况下,单纯的使用
Gossip
协议来传播节点的已下线信息会给节点的信息更新带来一定的延迟,因为这个协议通常需要一段时间才能传播至整个集群,而发送Fail消息会让集群里的所有节点立即知道某个主节点已经下线,从而尽快判断是否需要将集群标记为下线,又或者对下线主节点进行故障转移。