个人主页:道友老李
接待到场社区:道友老李的学习社区
Redis集群
Smart客户端
smart客户端原理
大多数开辟语言的Redis客户端都采用Smart 客户端支持集群协议。Smart客户端通过在内部维护 slot →node的映射关系,本地就可实现键到节点的查找,从而包管IO效率的最大化,而MOVED重定向负责协助Smart客户端更新slot →node映射。Java的Jedis就默认实现了这个功能
ASK 重定向
1.客户端ASK 重定向流程
Redis集群支持在线迁移槽(slot)和数据来完成程度伸缩,当slot对应的数据从源节点到目的节点迁移过程中,客户端必要做到智能辨认,包管键命令可正常实行。例如当一个slot数据从源节点迁移到目的节点时,期间可能出现一部分数据在源节点,而另一部分在目的节点。
当出现上述情况时,客户端键命令实行流程将发生变化:
1)客户端根据本地slots缓存发送命令到源节点,假如存在键对象则直接实行并返回效果给客户端。
2)假如键对象不存在,则可能存在于目的节点,这时源节点会回复ASK重定向异常。格式如下 error) ASK (slot} {targetIP}:{targetPort}。
3)客户端从ASK重定向异常提取出目的节点信息,发送asking命令到目的节点打开客户端毗连标识,再实行键命令。假如存在则实行,不存在则返回不存在信息。
ASK与MOVED虽然都是对客户端的重定向控制,但是有着本质区别。ASK重定向阐明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只能是临时性的重定向,客户端不会更新slots缓存。但是MOVED重定向阐明键对应的槽已经明白指定到新的节点,因此必要更新slots缓存。
集群下的Jedis客户端
参见模块redis-cluster。
同时集群下的Jedis客户端只能支持有限的有限的批量操作,必须要求全部key的slot值相称。这时可以思量使用hash tags。
Hash tags
集群支持hash tags功能,即可以把一类key定位到同一个slot,tag的标识目前不支持设置,只能使用{},redis处置惩罚hash tag的逻辑也很简单,redis只计算从第一次出现{,到第一次出现}的substring的hash值,substring为空,则仍然计算整个key的值。
比如这两个键{user1000}.following 和 {user1000}.followers 会被哈希到同一个哈希槽里,因为只有 user1000 这个子串会被用来计算哈希值。
对于 foo{}{bar} 这个键,整个键都会被用来计算哈希值,因为第一个出现的 { 和它右边第一个出现的 } 之间没有任何字符。
对于 foo{bar}{zap} 这个键,用来计算哈希值的是 bar 这个子串。
我们在使用hashtag特性时,肯定要注意,不能把key的离散性变得非常差。
比如,没有利用hashtag特性之前,key是这样的:mall:sale:freq:ctrl:860000000000001,很明显这种key由于与用户相关,所以离散性非常好。
而使用hashtag以后,key是这样的:mall:sale:freq:ctrl:{860000000000001},这种key还是与用户相关,所以离散性依然非常好。
我们万万不要这样来使用hashtag特性,例如将key设置为:mall:{sale:freq:ctrl}:860000000000001。
这样的话,无论有多少个用户多少个key,其{}中的内容完全一样都是sale:freq:ctrl,也就是说,全部的key都会落在同一个slot上,导致整个Redis集群出现严峻的倾斜问题。
集群原理
节点通信
通信流程
在分布式存储中必要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态信息。常见的元数据维护方式分为:会合式和P2P方式。Redis集群采用P2P的Gossip(流言)协议,Gossip协议工作原理就是节点相互不停通信交换信息,一段时间后全部的节点都会知道集群完整的信息,这种方式类似流言流传。
通信过程阐明:
1)集群中的每个节点都会单独开辟一个TCP通道,用于节点之间相互通信,通信端口号在根本端口上加10000。
2)每个节点在固定周期内通过特定规则选择几个节点发送ping消息。
3)接收到ping消息的节点用pong消息作为相应。
集群中每个节点通过肯定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点相互可以正常通信,最终它们会到达一致的状态。当节点出故障、新节点到场、主从脚色变化、槽信息变更等事件发生时,通过不停的ping/pong消息通信,经过一段时间后全部的节点都会知道整个集群全部节点的最新状态,从而到达集群状态同步的目的。
Gossip 消息
Gossip协议的主要职责就是信息交换。信息交换的载体就是节点相互发送的Gossip消息,相识这些消息有助于我们明白集群如何完成信息交换。
常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息等,
meet消息:
用于通知新节点到场。消息发送者通知接收者到场到当前集群,meet消息通信正常完成后,接收节点会到场到集群中并进行周期性的ping、pong消息交换。
ping消息:
集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换相互状态信息。ping消息发送封装了自身节点和部分其他节点的状态数据。
pong消息:
当接收到ping、meet消息时,作为相应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。
fail消息:
当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。
全部的消息格式分别为:消息头和消息体。消息头包罗发送节点自身状态数据,接收节点根据消息头就可以获取到发送节点的相关数据。
集群内全部的消息都采用类似的消息头结构clusterMsg,它包罗了发送节点关键信息,如节点id、槽映射、节点标识(主从脚色,是否下线)等。消息体在Redis内部采用clusterMsg Data 结构声明。
消息体clusterMsgData界说发送消息的数据,其中ping,meet、pong都采用clusterMsgDataGossip数组作为消息体数据,实际消息类型使用消息头的type属性区分。每个消息体包罗该节点的多个clusterMsgDataGossip结构数据,用于信息交换。
当接收到ping、meet消息时,接收节点会解析消息内容并根据自身的辨认情况做出相应处置惩罚。
节点选择
虽然Gossip协议的信息交换机制具有天然的分布式特性,但它是有成本的。由于内部必要频繁地进行节点信息交换,而ping/pong消息会携带当前节点和部分其他节点的状态数据,势必会加重带宽和计算的负担。Redis集群内节点通信采用固定频率(定时任务每秒实行10次)。
因此节点每次选择必要通信的节点列表变得非常紧张。通信节点选择过多虽然可以做到信息实时交换但成本过高。节点选择过少会降低集群内全部节点相互信息交换频率,从而影响故障判定、新节点发现等需求的速率。因此Redis集群的Gossip协议必要兼顾信息交换实时性和成本开销。
消息交换的成本主要体现在单元时间选择发送消息的节点数量和每个消息携带的数据量。
1.选择发送消息的节点数量
集群内每个节点维护定时任务默认间隔1秒,每秒实行10次,定时任务里每秒随机选取5个节点,找出最久没有通信的节点发送ping消息,用于包管 Gossip信息交换的随机性。同时每100毫秒都会扫描本地节点列表,假如发现节点最近一次接受pong消息的时间大于cluster_node_timeout/2,则立刻发送ping消息,防止该节点信息太长时间未更新。
根据以上规则得出每个节点每秒必要发送ping消息的数量= 1 +10
- num(node.pong_received >cluster_node_timeout/2),因此cluster_node_timeout参数对消息发送的节点数量影响非常大。当我们的带宽资源告急时,可以得当调大这个参数,如从默认15秒改为30秒来降低带宽占用率。过度调大cluster_node_timeout 会影响消息交换的频率从而影响故障转移、槽信息更新、新节点发现的速率。因此必要根据业务容忍度和资源斲丧进行均衡。同时整个集群消息总交换量也跟节点数成正比。
⒉消息数据量
每个ping消息的数据量体现在消息头和消息体中,其中消息头主要占用空间的字段是myslots [CLUSTER_SLOTS/8],占用2KB,这块空间占用相对固定。消息领会携带肯定命量的其他节点信息用于信息交换。
根消息体携带数据量跟集群的节点数痛痒相关,更大的集群每次消息通信的成本也就更高,因此对于Redis集群来说并不是大而全的集群更好。
故障转移
Redis集群自身实现了高可用。高可用首先必要解决集群部分失败的场景:当集群内少量节点出现故障时通过自动故障转移包管集群可以正常对外提供服务。
故障发现
当集群内某个节点出现问题时,必要通过一种健壮的方式包管辨认出节点是否发生了故障。Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以流传节点槽信息,还可以流传其他状态如:主从状态、节点故障等。因此故障发现也是通过消息流传机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)。
主观下线:
指某个节点以为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
客观下线:
指标记一个节点真正的下线,集群内多个节点都以为该节点不可用,从而达成共识的效果。假如是持有槽的主节点故障,必要为该节点进行故障转移。
主观下线
集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为相应。假如在cluster-node-timeout时间内通信一直失败,则发送节点会以为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。
流程阐明:
1)节点a发送ping消息给节点b,假如通信正常将接收到pong消息,节点 a更新最近一次与节点b的通信时间。
2)假如节点 a与节点b通信出现问题则断开毗连,下次会进行重连。假如一直通信失败,则节点a记录的与节点b最后通信时间将无法更新。
3)节点a内的定时任务检测到与节点b最后通信时间超高cluster-node-timeout时,更新本地对节点b的状态为主观下线(pfail)。
主观下线简单来讲就是,当cluster-note-timeout时间内某节点无法与另一个节点顺利完成ping消息通信时,则将该节点标记为主观下线状态。每个节点内的clusterstate结构都必要保存其他节点信息,用于从自身视角判定其他节点的状态。
Redis集群对于节点最终是否故障判定非常严谨,只有一个节点以为主观下线并不能准确判定是否故障。
比如节点6379与6385通信停止,导致6379判定6385为主观下线状态,但是6380与6385节点之间通信正常,这种情况不能判定节点6385发生故障。因此对于一个健壮的故障发现机制,必要集群内大多数节点都判定6385故障时,才能以为6385确实发生故障,然后为6385节点进行故障转移。而这种多个节点协作完成故障发现的过程叫做客观下线。
客观下线
当某个节点判定另一个节点主观下线后,相应的节点状态会跟随消息在集群内流传。
ping/pong消息的消息领会携带集群1/10的其他节点状态数据,当接受节点发现消息体中含有主观下线的节点状态时,会在本地找到故障节点的ClusterNode结构,保存到下线报告链表中。
通过Gossip消息流传,集群内节点不停收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。这里有两个问题:
1)为什么必须是负责槽的主节点到场故障发现决议?因为集群模式下只有处置惩罚槽的主节点才负责读写请求和集群槽等关键信息维护,而从节点只进行主节点数据和状态信息的复制。
2)为什么半数以上处置惩罚槽的主节点?必须半数以上是为了应对网络分区等原因造成的集群分割情况,被分割的小集群因为无法完成从主观下线到客观下线这一关键过程,从而防止小集群完成故障转移之后继续对外提供服务。
尝试客观下线
集群中的节点每次接收到其他节点的pfail状态,都会尝试触发客观下线,
流程阐明:
1)首先统计有用的下线报告数量,假如小于集群内持有槽的主节点总数的一半则退出。
2)当下线报告大于槽主节点数量一半时,标记对应故障节点为客观下线状态。
3)向集群广播一条fail消息,通知全部的节点将故障节点标记为客观下线,fail消息的消息体只包罗故障节点的ID。
广播fail消息是客观下线的最后一步,它承担着非常紧张的职责:
通知集群内全部的节点标记故障节点为客观下线状态并立刻生效。
通知故障节点的从节点触发故障转移流程。
故障恢复
故障节点变为客观下线后,假如下线节点是持有槽的主节点则必要在它的从节点中选出一个替换它,从而包管集群的高可用。下线主节点的全部从节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程。
资格查抄
每个从节点都要查抄最后与主节点断线时间,判定是否有资格替换故障的主节点。假如从节点与主节点断线时间高出cluster-node-time * cluster-slave-validity-factor,则当前从节点不具备故障转移资格。参数cluster-slave-validity-factor用于从节点的有用因子,默以为10。
准备选举时间
当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该时间后才能实行后续流程。
这里之所以采用延迟触发机制,主要是通过对多个从节点使用差别的延迟选举时间来支持优先级问题。复制偏移量越大阐明从节点延迟越低,那么它应该具有更高的优先级来替换故障主节点。
全部的从节点中复制偏移量最大的将提前触发故障选举流程。
主节点b进入客观下线后,它的三个从节点根据自身复制偏移量设置延迟选举时间,如复制偏移量最大的节点slave b-1延迟1秒实行,包管复制延迟低的从节点优先发起选举。
发起选举
当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程如下:
(1)更新设置纪元
设置纪元是一个只增不减的整数,每个主节点自身维护一个设置纪元(clusterNode .configEpoch)标示当前主节点的版本,全部主节点的设置纪元都不相称,从节点会复制主节点的设置纪元。整个集群又维护一个全局的设置纪元(clusterstate.currentEpoch),用于记录集群内全部主节点设置纪元的最大版本。实行cluster info命令可以查看设置纪元信息:
设置纪元的主要作用:
标示集群内每个主节点的差别版本和当前集群最大的版本。
每次集群发生紧张事件时,这里的紧张事件指出现新的主节点(新到场的或者由从节点转换而来),从节点竞争选举。都会递增集群全局的设置纪元并赋值给相关主节点,用于记录这一关键事件。
主节点具有更大的设置纪元代表了更新的集群状态,因此当节点间进行ping/pong消息交换时,如出现slots等关键信息不一致时,以设置纪元更大的一方为准,防止过时的消息状态污染集群。
设置纪元的应用场景有:
新节点到场。槽节点映射辩论检测。从节点投票选举辩论检测。
选举投票
只有持有槽的主节点才会处置惩罚故障选举消息(FAILOVER_AUTH_REQUEST),因为每个持有槽的节点在一个设置纪元内都有唯一的一张选票,当接到第一个请求投票的从节点消息时回复FAILOVER_AUTH_ACK消息作为投票,之后类似设置纪元内其他从节点的选举消息将忽略。
投票过程其实是一个领导者选举的过程,如集群内有N个持有槽的主节点代表有N张选票。由于在每个设置纪元内持有槽的主节点只能投票给一个从节点,因此只能有一个从节点获得 N/2+1的选票,包管能够找出唯一的从节点。
Redis集群没有直接使用从节点进行领导者选举,主要因为从节点数必须大于等于3个才能包管凑够N/2+1个节点,将导致从节点资源浪费。使用集群内全部持有槽的主节点进行领导者选举,即使只有一个从节点也可以完成选举过程。
当从节点收集到N/2+1个持有槽的主节点投票时,从节点可以实行替换主节点操作,例如集群内有5个持有槽的主节点,主节点b故障后还有4个,当其中一个从节点收集到3张投票时代表获得了充足的选票可以进行替换主节点操作,。
投票作废:每个设置纪元代表了一次选举周期,假如在开始投票之后的cluster-node-timeout*2时间内从节点没有获取充足数量的投票,则本次选举作废。从节点对设置纪元自增并发起下一轮投票,直到选举成功为止。
替换主节点
当从节点收集到充足的选票之后,触发替换主节点操作:
1)当前从节点取消复制变为主节点。
2)实行clusterDelslot 操作打消故障主节点负责的槽,并实行clusterAddSlot把这些槽委派给自己。
3)向集群广播自己的pong消息,通知集群内全部的节点当前从节点变为主节点并接管了故障主节点的槽信息。
故障转移时间
在先容完故障发现和恢复的流程后,这时我们可以估算出故障转移时间:
1)主观下线(pfail)辨认时间=cluster-node-timeout。
2)主观下线状态消息流传时间<=cluster-node-timeout/2。消息通信机制对高出cluster-node-timeout/2未通信节点会发起ping消息,消息体在选择包罗哪些节点时会优先选取下线状态节点,所以通常这段时间内能够收集到半数以上主节点的pfail 报告从而完成故障发现。
3)从节点转移时间<=1000毫秒。由于存在延迟发起选举机制,偏移量最大的从节点会最多延迟1秒发起选举。通常第一次选举就会成功,所以从节点实行转移时间在1秒以内。
根据以上分析可以预估出故障转移时间,如下:
failover-time(毫秒)≤cluster-node-timeout
- cluster-node-timeout/2 + 1000
因此,故障转移时间跟cluster-node-timeout参数痛痒相关,默认15秒。设置时可以根据业务容忍度做出得当调整,但不是越小越好。
集群不可用判定
为了包管集群完整性,默认情况下当集群16384个槽任何一个没有指派到节点时整个集群不可用。实行任何键命令返回( error)CLUSTERDOWN Hash slot not served错误。这是对集群完整性的一种保护措施,包管全部的槽都指派给在线的节点。但是当持有槽的主节点下线时,从故障发现到自动完成转移期间整个集群是不可用状态,对于大多数业务无法容忍这种情况,因此可以将参数cluster-require-full-coverage设置为no,当主节点故障时只影响它负责槽的相关命令实行,不会影响其他主节点的可用性。
但是从集群的故障转移的原理来说,集群会出现不可用,当:
1、当访问一个 Master 和 Slave 节点都挂了的时候,cluster-require-full-coverage=yes,会报槽无法获取。
2、集群主库半数宕机(根据 failover 原理,fail 掉一个主必要一半以上主都投票通过才可以)。
另外,当集群 Master 节点个数小于 3 个的时候,或者集群可用节点个数为偶数的时候,基于 fail 的这种选举机制的自动主从切换过程可能会不能正常工作,一个是标记 fail 的过程,一个是选举新的 master 的过程,都有可能异常。
集群读写分离
1.只读毗连
集群模式下从节点不接受任何读写请求,发送过来的键命令会重定向到负责槽的主节点上(其中包括它的主节点)。当必要使用从节点分担主节点读压力时,可以使用readonly命令打开客户端毗连只读状态。之前的复制设置slave-read-only在集群模式下无效。当开启只读状态时,从节点接收读命令处置惩罚流程变为:假如对应的槽属于自己正在复制的主节点则直接实行读命令,否则返回重定向信息。
readonly命令是毗连级别生效,因此每次新建毗连时都必要实行readonly开启只读状态。实行readwrite命令可以关闭毗连只读状态。
2.读写分离
集群模式下的读写分离,同样会遇到:复制延迟,读取逾期数据,从节点故障等问题。针对从节点故障问题,客户端必要维护可用节点列表,集群提供了cluster slaves {nodeld}命令,返回nodeId对应主节点下全部从节点信息,命令如下:
cluster slave
41ca2d569068043a5f2544c598edd1e45a0c1f91
解析以上从节点列表信息,清除fail状态节点,这样客户端对从节点的故障判定可以委托给集群处置惩罚,简化维护可用从节点列表难度。
同时集群模式下读写分离涉及对客户端修改如下:
1)维护每个主节点可用从节点列表。
2)针对读命令维护请求节点路由。
3)从节点新建毗连开启readonly状态。
集群模式下读写分离成本比较高,可以直接扩展主节点数量提高集群性能,一般不建议集群模式下做读写分离。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |