由于对可用性、可靠性要求较高,则需要引入Redis
的集群方案。
1、Redis
集群搭建模式?
- 主从模式
- 哨兵模式
Cluster
模式
2、主从模式
答:MySQL
需要主从复制的原因一样,Redis
虽然读写速度非常快,但是也会产生性能瓶颈,特别是在读压力上,为了分担压力,Redis
支持主从复制。Redis
的主从结构一主一从,一主多从或级联结构,复制类型可以根据是否是全量而分为全量同步和增量同步。
2.1、工作机制
slave
启动后,向master
发送SYNC
命令,master
接收到SYNC
命令后通过bgsave
保存快照(即RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令;master
将保存的快照文件发送给slave
,并继续记录执行的写命令;slave
接收到快照文件后,加载快照文件,载入数据;master
快照发送完后开始向slave
发送缓冲区的写命令,slave
接收命令并执行,完成复制初始化;- 此后
master
每次执行一个写命令都会同步发送给slave
,保持master
与slave
之间数据的一致性;
2.2、主从复制的优点
master
能自动将数据同步到slave
,可以进行读写分离,分担master
的读压力;master
、slave
之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求;
2.3、主从复制的缺点
- 不具备自动容错与恢复功能,
master
或save
的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP
才能恢复 master
宕机,如果宕机前数据没有同步完,则切换IP
后会存在数据不一致的问题- 难以支持在线扩容,
Redis
的容量受限于单机配置
3、Redis
主从复制的实现?
答: 主从复制可以根据需要分为全量同步的增量同步两种方式。
3.1、全量同步
答:Redis
全量复制一般发生在 slave
的初始阶段,这时 slave
需要将 master
上的数据都复制一份,具体步骤如下:
1)slave
连接 master
,发送 SYNC
命令;
2)master
接到 SYNC
命令后执行 BGSAVE
命令生产 RDB
文件,并使用缓冲区记录此后执行的所有写命令;
3)master
执行完 BGSAVE
后,向所有的 slave
发送快照文件,并在发送过程中继续记录执行的写命令;
4)slave
收到快照后,丢弃所有的旧数据,载入收到的数据;
5)master
快照发送完成后就会开始向 slave
发送缓冲区的写命令;
6)slave
完成对快照的载入,并开始接受命令请求,执行来自 master
缓冲区的写命令;
7)slave
完成上面的数据初始化后就可以开始接受用户的读请求了。
3.2、增量同步
答: 增量复制实际上就是在 slave
初始化完成后开始正常工作时 master
发生写操作同步到 slave
的过程。增量复制的过程主要是 master
每执行一个写命令就会向 slave
发送相同的写命令,slave
接受并执行写命令,从而保持主从一致。
3.3、Redis
的主从同步策略?
答: 主从同步刚连接的时候进行全量同步,全量同步结束后开始增量同步。
如果有需要,slave
在任何时候都可以发起全量同步,其主要策略就是无论如何首先会尝试进行增量同步,如果失败则会要求 slave
进行全量同步,之后再进行增量同步。
如果多个 slave
同时断线需要重启的时候,因为只要 slave
启动,就会和 master
建立连接发送SYNC
请求和主机全量同步,如果多个同时发送 SYNC
请求,可能导致 master IO
突增而发送宕机。所以我们要避免多个 slave
同时恢复重启的情况。
4、哨兵模式
答: 在主从复制实现之后,如果想对 master
进行监控,Redis
提供了一种哨兵机制,哨兵的含义就是监控 Redis
系统的运行状态,通过投票机制,从 slave
中选举出新的 master
以保证集群正常运行。
还可以启用多个哨兵进行监控以保证集群足够稳健,这种情况下,哨兵不仅监控主从服务,哨兵之间也会相互监控。
4.1、哨兵的作用
- 监控
master
、slave
是否正常运行 - 当
master
出现故障时,能自动将一个slave
转换为master
(大哥挂了,选一个小弟上位) - 多个哨兵可以监控同一个
Redis
,哨兵之间也会自动监控
4.2、哨兵模式的原理
答: 哨兵主要用于管理多个 Redis
服务器,主要有以下三个任务:监控、提醒以及故障转移。
每个哨兵会向其它哨兵、master
、slave
定时发送消息,以确认对方是否还存活。如果发现对方在配置的指定时间内未回应,则暂时认为对方已挂。若“哨兵群”中的多数 sentinel
都报告某一master
没响应,系统才认为该 master
“彻底死亡”,通过一定的 vote
算法从剩下的 slave
节点中选一台提升为 master
,然后自动修改相关配置。
4.3、哨兵模式故障迁移流程
- 首先是从主服务器的从服务器中选出一个从服务器作为新的主服务器。
- 选出之后通过
slaveif no ont
将该从服务器升为新主服务器; - 然后再通过
slaveof ip port
命令让其他从服务器复制该信主服务器。
选点的依据:
答: 网络连接正常 -> 5 秒内回复过 INFO
命令 -> 10*down - after - milliseconds
内与主连接过的 -> 从服务器优先级 -> 复制偏移量 -> 运行id
较小的。选举采用Raft
算法:
- 发现
master
下线的哨兵节点(我们称他为A
)向每个哨兵发送命令,要求对方选自己为领头哨兵; - 如果目标哨兵节点没有选过其他人,则会同意选举
A
为领头哨兵; - 如果有超过一半的哨兵同意选举
A
为领头,则A
当选; - 如果有多个哨兵节点同时参选领头,此时有可能存在一轮投票无竞选者胜出,此时每个参选的节点等待一个随机时间后再次发起参选请求,进行下一轮投票竞选,直至选举出领头哨兵;
4.4、优点
- 哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有
- 哨兵模式下,
master
挂掉可以自动进行切换,系统可用性更高
4.5、缺点:
- 主从服务器的数据要经常进行主从复制,这样会造成性能下降
- 当主服务器宕机后,从服务器切换成主服务器的那段时间,服务是不可用的
5、Cluster
模式
5.1、Cluster
模式的原理?
答: 其实现原理就是一致性 Hash
。Redis Cluster
中有一个 16384 长度的槽的概念,他们的编号为 0、1、2、3 …… 16382、16383
。这个槽是一个虚拟的槽,并不是真正存在的。正常工作的时候,Redis Cluster
中的每个 Master
节点都会负责一部分的槽,当有某个 key
被映射到某个 Master
负责的槽,那么这个 Master
负责为这个 key
提供服务。至于哪个 Master
节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rb
脚本)。这里值得一提的是,在 Redis Cluster
中,只有 Master
才拥有槽的所有权,如果是某个 Master
的 slave
,这个slave
只负责槽的使用,但是没有所有权。
5.2、什么是一致性 Hash
以及解决什么问题?
答: 一致性 Hash
其实是普通 Hash
算法的改良版,其 Hash
计算方法没有变化,但是 Hash
空间发生了变化,由原来的线性的变成了环。
缓存 key
通过 Hash
计算之后得到在 Hash
环中的位置,然后顺时针方向找到第一个节点,这个节点就是存放 key
的节点。
由此可见,一致性Hash
主要是为了解决普通Hash
中扩容和宕机的问题。
同时还可以通过虚拟节点来解决数据倾斜的问题:就是在节点稀疏的 Hash
环上对物理节点虚拟出一部分虚拟节点,key
会打到虚拟节点上面,而虚拟节点上的 key
实际也是映射到物理节点上的,这样就避免了数据倾斜导致单节点压力过大导致节点雪崩的问题。
4.3、Cluster
的分片机制?
答: 为了使得集群能够水平扩展,首要解决的问题就是如何将整个数据集按照一定的规则分配到多个节点上。对于客户端请求的 key
,根据公式 HASH_SLOT=CRC16(key) mod 16384
,计算出映射到哪个分片上。而对于 CRC16
算法产生的 hash
值会有 16bit
,可以产生2^16-=65536
个值。
Redis
集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis
集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。
4.4、Cluster
的特点
- 所有的
Redis
节点彼此互联(PING-PONG
机制),内部使用二进制协议优化传输速度和带宽; - 节点的
fail
是通过集群中超过半数的节点检测失效时才生效; - 客户端与
Redis
节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
4.5、工作机制
- 在
Redis
的每个节点上,都有一个插槽(slot
),取值范围为0-16383
; - 当我们存取
key
的时候,Redis
会根据CRC16
的算法得出一个结果,然后把结果对16384
求余数,这样每个key
都会对应一个编号在0-16383
之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作; - 为了保证高可用,
Cluster
模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点; - 当其它主节点
ping
一个主节点A
时,如果半数以上的主节点与A
通信超时,那么认为主节点A
宕机了。如果主节点A
和它的从节点都宕机了,那么该集群就无法再提供服务了。
4.6、优点
- 无中心架构,数据按照
slot
分布在多个节点; - 集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据;
- 可线性扩展到1000多个节点,节点可动态添加或删除;
- 能够实现自动故障转移,节点之间通过
gossip
协议交换状态信息,用投票机制完成slave
到master
的角色转换。
4.7、缺点
- 客户端实现复杂,驱动要求实现
Smart Client
,缓存slots mapping
信息并及时更新,提高了开发难度。目前仅JedisCluster
相对成熟,异常处理还不完善,比如常见的“max redirect exception
”; - 节点会因为某些原因发生阻塞(阻塞时间大于
cluster-node-timeout
)被判断下线,这种failover
是没有必要的; - 数据通过异步复制,不保证数据的强一致性;
slave
充当“冷备”,不能缓解读压力;- 批量操作限制,目前只支持具有相同
slot
值的key
执行批量操作,对mset
、mget
、sunion
等操作支持不友好; key
事务操作支持有线,只支持多key
在同一节点的事务操作,多key
分布不同节点时无法使用事务功能。
5、集群扩容
答: 当一个 Redis
新节点运行并加入现有集群后,我们需要为其迁移槽和数据。首先要为新节点指定槽的迁移计划,确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据均匀。
1)首先启动一个 Redis
节点,记为 M4
。
2)使用 cluster meet
命令,让新 Redis
节点加入到集群中。新节点刚开始都是主节点状态,由于没有负责的槽,所以不能接受任何读写操作,后续给他迁移槽和填充数据。
3)对 M4
节点发送 cluster setslot { slot } importing { sourceNodeId }
命令,让目标节点准备导入槽的数据。
4)对源节点,也就是 M1,M2,M3
节点发送 cluster setslot { slot } migrating { targetNodeId }
命令,让源节>点准备迁出槽的数据。
4)源节点执行 cluster getkeysinslot { slot } { count }
命令,获取 count
个属于槽 { slot }
的键,然后执行步骤>六的操作进行迁移键值数据。
5)在源节点上执行 migrate { targetNodeIp} " " 0 { timeout } keys { key… }
命令,把获取的键通过 pipeline
机制>批量迁移到目标节点,批量迁移版本的 migrate 命令在 Redis 3.0.6 以上版本提供。
6)重复执行步骤 5 和步骤 6 直到槽下所有的键值数据迁移到目标节点。
7)向集群内所有主节点发送 cluster setslot { slot } node { targetNodeId }
命令,通知槽分配给目标节点。为了>保证槽节点映射变更及时传播,需要遍历发送给所有主节点更新被迁移的槽执行新节点。
6、集群收缩
答: 收缩节点就是将 Redis
节点下线,整个流程需要如下操作流程。
1)首先需要确认下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
2)当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记改节点后可以正常关闭。
7、集群的故障发现
答: 当集群内某个节点出现问题时,需要通过一种健壮的方式保证识别出节点是否发生了故障。Redis 集群内节点通过 ping/pong
消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的。 主要环节包括:
- 主观下线(
PFAIL-Possibly Fail
): 集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong
消息作为响应。如果在cluster-node-timeout
时间内通信一直失败,则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(PFail
)状态。 - 客观下线(
Fail
):Redis
集群对于节点最终是否故障判断非常严谨,只有一个节点认为主观下线并不能准确判断是否故障。当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播,通过Gossip
消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。
8、故障恢复
答: 故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的所有从节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程。
9、Redis
常见性能问题和解决方案?
Master
最好不要做任何持久化工作,如RDB
内存快照和AOF
日志文件- 如果数据比较重要,某个
Slave
开启AOF
备份数据,策略设置为每秒同步一次 - 为了主从复制的速度和连接的稳定性,
Master
和Slave
最好在同一个局域网内 - 尽量避免在压力很大的主库上增加从库
10、如何实现分布式锁
- 「互斥性」: 任意时刻,只有一个客户端能持有锁
- 「锁超时释放」: 持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁
- 「可重入性」: 一个线程如果获取了锁之后,可以再次对其请求加锁
- 「高性能和高可用」: 加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效
- 「安全性」: 锁只能被持有的客户端删除,不能被其他客户端删除
Redison
底层原理:
11、RedLock
加锁步骤
- 按顺序向集群中所有
master
节点请求加锁; - 根据设置的超时时间来判断,是不是要跳过该
master
节点; - 如果大于等于半数节点
( N/2+1 )
加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦; - 如果获取锁失败,解锁!
12、大Key优化
13、热点 key
优化
答: 热key
带来的问题:请求到的分片过于集中,超过单台 Server
的性能极限。
解决方案:
1)服务端缓存:即将热点数据缓存至服务端的内存中;
2)备份热点Key
:即将热点Key+
随机数,随机分配至 Redis
其它节点中。
14、脑裂现象
答:Redis
的集群脑裂是指因为网络问题,导致redis master
节点跟redis slave
节点和sentinel
集群处于不同的网络分区,此时因为sentinel
集群无法感知到master
的存在,所以将slave
节点提升为master
节点.此时存在两个不同的master
节点,就像一个大脑分裂成了两个。
集群脑裂问题中,如果客户端还在基于原来的master
节点继续写入数据,那么新的master
节点将无法同步这些数据,当网络问题解决之后,sentinel
集群将原先的master
节点降为slave
节点,此时再从新的master
中同步数据,将会造成大量的数据丢失。
解决方案
- 第一个参数表示连接到
master
的最少slave
数量 - 第二个参数表示
slave
连接到master
的最大延迟时间
min-replicas-to-write 3
min-replicas-max-lag 10
15、读写分离模型
答: 通过增加 Slave DB
的数量,读的性能可以线性增长。 为了避免Master DB
的单点故障,集群一般都会采用两台Master DB
做双机热备,所以整个集群的读和写的可用性都非常高。读写分离架构的缺陷在于,不管是 Master
还是 Slave
,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive
类型的应用,读写分离架构并不适合。