面试问Redis集群,被虐的不行了......
❝哨兵主要针对单节点故障无法自动恢复的解决方案,集群主要针对单节点容量、并发问题、线性可扩展性的解决方案。本文使用官方提供的redis cluster。文末有你们想要的设置ssh背景哦!
❞
前言
❝咔咔整理了一个路线图,打造一份面试宝典,准备按照这样的路线图进行编写文章,后期发现没有补充到的知识点在进行添加。也期待各位伙伴一起来帮助补充一下。评论区见哦!
❞
本文主要围绕如下几个方面介绍集群
集群简介 集群作用 配置集群 手动、自动故障转移 故障转移原理
本文实现环境
一、集群简介
集群是为了解决主从复制中单机内存上限和并发问题,假如你现在的云服务内存为256GB,当达到这个内存时redis就没办法再提供服务,同时数据量能达到这个地步写数据量也会很大,容易造成缓冲区溢出,造成从节点无限的进行全量复制导致主从无法正常工作。
那么我们就需要把单机的主从改为多对多的方式并且所有的主节点都会连接在一起互相通信。这样的方式既可以分担单机内存,也可以分发请求,提高系统的可用性。
如图:当有大量请求写入时,不再会单一的向一个主节点发送指令,而会把指令进行分流到各个主节点,达到分担内存、避免大量请求的作用。
那么指令是如何进行分流存储的呢!我们就需要到集群存储结构中一探究竟。
二、集群作用
分散单机的存储能力,同时也可以很方便的实现扩展。 分流单机的访问请求 提高系统的可用性
如何理解提高系统的可用性这句话,我们看下图,当master1宕机后对系统的影响不会那么大,仍然可以提供正常的服务。
这个时候就会有人问了,当master1宕机后集群这个时候怎么工作呀!这个问题会在下文的故障转移来给你解答。并且在原理篇会对这个问题进行详解
三、集群存储结构
1. 存储结构
单机的存储是当用户发起请求后直接把key存储到自己的内存即可。集群的存储结构就没有那么简单了,首先当用户发起一个key指令后需要做的事情。
通过CRC16(key)会计算出来一个值 用这个值取模16384,会得到一个值,我们就先认为是28 这个值28就是key保存的空间位置
那么现在问题来了,这个key到底应该存储在那个redis存储空间里边呢!
其实redis在集群启动后就已经把存储空间划分了16384
份,每台主机保存一部分。
这里需要注意的是我给每个redis存储空间里边的编号就相当于一个小的存储空间(专业术语“哈希槽”
),你可以理解为一栋楼里边的编号,一栋楼就是redis的整个存储空间,每个房子的编号就相当于一个存储空间,这个存储空间会有一定的区域来保存对应的key,并非上图取模后的位置。
箭头指向的28是指的28会存储在这个区域里,这个房子有可能会存储29、30、31等。
此时问题来了,如果新增、减少一台机器后怎么办呢!看图说话,能用图说明尽量不去用文字。
在新增一台机器后,会从其他三个存储空间中拿出一定的槽分配给新的机器。这里可以自己设置想给新的机器放多少个槽。
同样减少一台机器后会把去掉的槽在重新分配给其它现有的机器跟新增节点一样,可以指定节点接收槽。
所谓的增节点或去节点就是改变槽所存储的位置不同。了解了集群的存储结构后,我们就需要在对另一个问题进行说明了,集群是如何设计内部通讯呢!来了一个值,获取一个key,去哪拿数据,跟着这个问题我们看下文。
2. 通讯设计
集群中的每个节点会在一定的时期给其它节点发送ping消息,其它节点返回pong作为响应。经过一段时间后所有节点都会知道集群全部节点的槽信息。
如下图有三个节点,那么就会把16384个哈希槽分成三份。
分别为0-5500、5501-11000、11001-16384
当用户发起了一个key的请求,集群是如何处理请求的呢!
下图的黑框代表这集群所有节点的槽信息,里边还有很多其它信息。
如图所示,用户发起请求key,redis接收后计算key的槽位置,在根据槽位置找出对应的节点
如果访问的槽就在节点本身,那么就会直接返回key对应数据。
否则会回复moved重定向错误, 并且给客户端返回正确的节点。
然后重发key指令
四、配置集群
1. 修改配置文件
只需要注意咔咔圈中的配置信息即可
cluster-enabled yes:开启集群模式 cluster-config-file nodes-6379.conf:集群配置文件 clustre-node-timeout 10000:节点超时时间,这里为了方便测试设置为10s
2. 构建6个节点的配置文件并全启动
咔咔给大家提供一个命令可以很方便的替换文件sed 's/6379/6380/g' 6379-redis.conf > 6380-redis.conf
按照这样的方式创建出来6个不同端口的配置文件
随便打开一个配置文件查看,检测是否替换成功为了查看日志信息方便,全部使用前台启动。并且查看服务是否都正常启动,执行命令ps -ef | grep redis
可以看到启动后多了个cluster标识,代表着都是集群的一个节点。所有节点启动完成,集群启动的指令需要基于ruby(咔咔使用redis版本为4.0)。接下来一起安装
3. 安装ruby
执行命令wget https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.gz
解压:tar -xvzf ruby-2.7.1.tar.gz
根据自己下载的版本来解压
安装:./configure | make | make install
这三个指令一气呵成。
查看ruby和gem版本:ruby -v
4. 启动集群
集群的执行命令在/usr/local/redis/src/redis-trib.rb
注意如果需要直接使用redis-trib.rb
命令,需要ln到bin目录下,否则就必须使用./redis-trib.rb
的方式。
如果按照步骤走,这里会出现一个错误执行gem install redis
很不幸的是在这里也会出现错误。随后需要安装yum install zlib-devel
和yum install openssl-devel
安装完成后,在/ruby-2.7.1/ext/openssl
和 /ruby-2.7.1/ext/zlib
分别执行ruby extconf.rb
并且执行make | make install
然后在执行gem install redis
就OK这时在回头来执行./redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384
「信息解读」
创建集群,并且给6个节点分配哈希槽,后三个节点配置为前三个节点的从节点显示每个节点的哈希槽信息和节点ID,最后一步需要输入yes
来到data目录下查看配置文件的变化。配置文件主要信息是每个主节点分的槽「查看主机点的运行日志」
这里给的主要信息cluster status changed:ok 集群状态正常
5. 集群设置与获取数据
当直接设置数据会报错,并且把name这个key进行转化后的槽位置为5798 并且给出了ip地址和端口号。需要使用命令redis-cli -c
在进行设置值的时候提示说重定向到5798的这个槽接下来进行获取数据,会自动的切换节点。
五、故障转移
1. 集群从节点下线
根据上文集群启动信息知道端口6383是6379的从节点。
接下来就是让6383下线查看6379的日志信息。
6379会报出连接6383丢失,并且给上标记fail,表示不可用。这个时候集群还是正常工作的。
「总结:从节点下线对集群没有影响」当端口6383上线后,所有的节点会把fail的标记清除
2. 集群主节点下线
手动下线主节点6379,查看从节点6383日志信息
此时的6383节点会持续连接6379共计10次。那为什么是10次呢!
是根据我们配置的参数cluster-node-timeout 10
来决定的,这里给我们一个信息就是一秒连接一次
直到时间到期后,开始故障转移。
这时6383在故障转移选举中胜任,翻身奴隶把歌唱,成为了主节点。此时在查看一下集群的节点信息,命令cluster nodes
。
会发现这里竟然存在四个主节点,但是其中一个主节点时下线状态「6379原主节点上线」
6379上线后,同样所有的节点也会清除fail信息。
并且节点信息也会改变,此时的6379改变为6383的从节点。
3. 新增主节点
在新增俩个端口6385和6386执行新增命令./redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
,这里发送的就是meet消息
执行add-node命令,第一个参数为新节点的ip+端口 第二个参数为已存在集群中的节点。根据下图我们就可以看到新增的节点已经存在集群中了。
「注意:虽说6385已经成为集群中的节点了,但是跟其它节点有区别。它没有数据,也就是没有哈希槽」接下来我们就需要把集群中的某些哈希槽分配到这个新节点上,分配结束后这个节点才会成为真正意义上的主节点
执行命令./redis-trib.rb reshard 127.0.0.1:6385
会提示转移多少个哈希槽并填写接收节点的id
最后一步询问是否从所有节点中转移:咔咔使用的是all
使用指令:cluster nodes
查看,6385的这个节点就已经拥有三个范围的哈希槽了
「主节点已经新增好了,接下来就需要给6385这个主节点配置一个从节点6386」
命令:./redis-trib.rb add-node --slave --master-id dcc0ec4d0c932ac5c35ae76af4f9c5d27a422d9f 127.0.0.1:6386 127.0.0.1:6385
master-id是6385的id,第一个参数为新节点的ip+端口 第二个为指定的主节点ip+端口
4. 手动故障迁移
当想对集群中的主节点进行升级的话可以手动执行故障转移到从节点,避免集群可用性受影响。
在从节点执行命令:cluster failover
「执行过程」
查看节点信息就可以看到6386这个节点已经成为了主机点。
当给从节点发送cluster failover 指令后,从节点会给主节点发送CLUSTERMSG_TYPE_MFSTART包。从节点请求主节点停止访问,从而对比两者的数据偏移量达到一致。
这时客户端不会连接我们淘汰的主节点,同时主节点向从节点发送复制偏移量,从节点得到复制偏移量后故障转移开始,接着通知主节点进行配置切换,当客户端在旧的master上解锁后重新连接到新的主节点上。
六、故障转移原理篇
上文中我们测试了故障转移,主节点下线后从节点变为主节点,接下来剖析这个过程。
1. 故障发现到确认
集群中的每个节点会定期的给其它节点发送ping消息,接收方用pong作为回复。
如果在cluster-node-timeout的时间内ping消息一直失败,则会把接收方的节点标记为pfail状态也就是主观下线。
这个下线状态是不是很熟悉。没错,这个跟哨兵判断主节点是否异常有点相似。当一个哨兵发现主节点有问题时也会标记主节点客观下线(s_down)。 突然发现跑题了,尴尬.......
在提一下哨兵,当一个哨兵认为主节点异常后标记主观下线,但是其它哨兵怎么能会同意,不能你说什么就是什么。都会去尝试连接异常的主节点,当半数以上的哨兵都认为主节点异常后会直接让其主节点客观下线。
同样集群也不会因为一个节点判断其状态为下线就行的,节点直接通过Gossip消息传播,集群中节点会不断收集故障节点的下线反馈并且存储到本地的故障节点下线报告中。当有半数以上的集群主节点都标记为主观下线后改变状态为客观下线。
最后向集群广播一条fail消息,通知所有节点将故障节点标记为客观下线。
例如:节点A发送ping到节点B通信异常后标记节点B为pfail,之后节点A会继续给节点C发送ping并且携带节点B的pfail信息然后节点C将节点B的故障保存到下线报告中。当下线报告数量大于有哈希槽主节点的一半数量以上后就会尝试客观下线。
2. 故障恢复(从节点从此翻身奴隶把歌唱)
当故障节点被定义为客观下线后,故障节点的所有从节点承担故障恢复的责任。
故障恢复是从节点通过定时任务发现自己的主机点客观下线后就会执行故障恢复流程。
「1. 资格检查」
所有的从节点都会进行检查与主节点最后的连接时间,断线时间大于cluster-node-time*cluster-slave-validity-factor时不具备故障转移的资格。
「2. 准备选举时间」
先说说为什么这里会有一个准备选举时间。
资格检查过后存在多个从节点,那么就需要使用不同的延迟选举时间来支持优先级。这里的优先级就是 以复制偏移量为基准,偏移量越大与故障主节点的延迟越小,那么就更有机会拥有替换主节点的机会。
主要的作用就是确保数据一致性最好的节点优先发起选举
「3.选举投票」
redis集群的投票机制没有采用从节点进行领导选