文章目录
四、集群伸缩
在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。原理:集群伸缩=槽和数据在节点之间的移动
扩容集群
1、准备新节点
新节点(主、从两个节点)作为孤儿节点运行,并没有其他节点与之通信。
2、加入集群
新节点依然采用cluster meet命令加入到现有集群中。
正式环境建议使用redis-trib.rb add-node
命令加入新节点,该命令内部会 执行新节点状态检查,如果新节点已经加入其他集群或者包含数据,则放弃 集群加入操作并打印错误信息。如果我们手动执行cluster meet命令加入已经存在于其他集群的节点,会造成被加入节点的集群合并到现有集群的情况,从而造成数据丢失和错乱, 后果非常严重,线上谨慎操作。
3、迁移槽和数据
- 槽迁移计划:迁移计划需要确保每个节 点负责相似数量的槽,从而保证各节点的数据均匀。
- 迁移数据:槽迁移计划确定后开始逐个把槽内数据从源节点迁移到目标节点。流程说明:
(1)对目标节点发送cluster setslot {slot} importing {sourceNodeId}命令,让目标节点准备导入槽的数据。
(2)对源节点发送cluster setslot {slot} migrating {targetNodeId}命令,让源节点准备迁出槽的数据。
(3)源节点循环执行cluster getkeysinslot {slot} {count}命令,获取count个属于槽{slot}的键。
(4)在源节点上执行migrate命令,把获取的键通过流水(pipeline)机制批量迁移到目标节点。
(5)重复执行步骤3和步骤4直到槽下所有的键值数据迁移到目标节点。
(6)向集群内所有主节点发送cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点。
收缩集群(类似扩容)
1、下线迁移槽
首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到 其他节点,保证节点下线后整个集群槽节点映射的完整性。
2、忘记下线节点
当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其 他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。
五、请求路由
本节我们关注集群请求路由的细节,以及客户端如何高效地操作集群。
请求重定向
在集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再 根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复 MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重定向。键命令执行步骤主要分两步:计算槽,查找槽所对应的节点。
Smart客户端
大多数开发语言的Redis客户端都采用Smart客户端支持集群协议,客户端如何选择见:http://redis.io/clients,从中找出符合自己要求的客户端类库。
Smart客户端通过在内部维护slot→node的映射关系,本地就可实现键到节点的查找,从而保证IO效率的最大化,而MOVED重定向负责协助Smart客户端更新slot→node映射。
ASK重定向
当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分数据在源节点,而另一部分在目标节点。
当出现上述情况时,客户端键命令执行流程将发生变化,如下所示:
- 客户端根据本地slots缓存发送命令到源节点,如果存在键对象则直 接执行并返回结果给客户端。
- 如果键对象不存在,则可能存在于目标节点,这时源节点会回复 ASK重定向异常。格式如下:(error)ASK{slot}{targetIP}:{targetPort}。
- 客户端从ASK重定向异常提取出目标节点信息,发送asking命令到目 标节点打开客户端连接标识,再执行键命令。如果存在则执行,不存在则返 回不存在信息。
ASK与MOVED虽然都是对客户端的重定向控制,但是有着本质区别。 ASK重定向说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移 完成,因此只能是临时性的重定向,客户端不会更新slots缓存。但是 MOVED重定向说明键对应的槽已经明确指定到新的节点,因此需要更新 slots缓存。
为了支持ASK重定向,源节点和目标节点在内部的clusterState结构中维 护当前正在迁移的槽信息,用于识别槽迁移情况。
使用smart客户端批量操作集群时,需要评估mget/mset、 Pipeline等方式在slot迁移场景下的容错性,防止集群迁移造成大量错误和数据丢失的情况。
集群环境下对于使用批量操作的场景,建议优先使用Pipeline方式,在客户端实现对ASK重定向的正确处理,这样既可以受益于批量操作的IO优 化,又可以兼容slot迁移场景。
六、故障转移
故障发现
- 主观下线:指某个节点认为另一个节点不可用,即下线状态,这个状 态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
- 客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节 点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节 点进行故障转移
故障恢复
故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它 的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的所有从 节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节 点进入客观下线时,将会触发故障恢复流程,如图10-39所示。
-
资格检查
每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障 的主节点。如果从节点与主节点断线时间超过cluster-node-time*cluster-slave-validity-factor,则当前从节点不具备故障转移资格。参数cluster-slave- validity-factor用于从节点的有效因子,默认为10。 -
准备选举时间
当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该 时间后才能执行后续流程。 -
发起选举
当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程:(1)更新配置纪元;(2)广播选举消息。
配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元(clusterNode.configEpoch)表示当前主节点的版本,所有主节点的配置纪元都不相等,从节点会复制主节点的配置纪元。
整个集群又维护一个全局的配置纪元(clusterState.currentEpoch),用于记录集群内所有主节点配置纪元的最大版本。
-
选举投票
每个持有槽的节点在一个配置纪元内都有唯一的一张选票,所以只有持有槽的主节点才会处理故障选举消息。
投票作废:每个配置纪元代表了一次选举周期,如果在开始投票之后的 cluster-node-timeout*2时间内从节点没有获取足够数量的投票,则本次选举 作废。 -
替换主节点
当从节点收集到足够的选票之后,触发替换主节点。
故障转移时间
预估出故障转移时间:failover-time(毫秒) ≤ cluster-node-timeout + cluster-node-timeout/2 + 1000
因此,故障转移时间跟cluster-node-timeout
参数息息相关,默认15秒。 配置时可以根据业务容忍度做出适当调整,但不是越小越好,下一节的带宽消耗部分会进一步说明。
七、集群运维
集群完整性
为了保证集群完整性,默认情况下当集群16384个槽任何一个没有指派 到节点时整个集群不可用。这是对集群完整性的一种保护措施,保证所有的 槽都指派给在线的节点。但是当持有槽的主节点下线时,从故障发现到自动 完成转移期间整个集群是不可用状态,对于大多数业务无法容忍这种情况, 因此建议将参数cluster-require-full-coverage
配置为no
,当主节点故障时只影 响它负责槽的相关命令执行,不会影响其他主节点的可用性。
带宽消耗
集群内Gossip消息通信本身会消耗带宽,官方建议集群最大规模在1000 以内,也是出于对消息通信成本的考虑,因此单集群不适合部署超大规模的节点。
集群带宽消耗主要分为:读写命令消耗+Gossip消息消耗。因此搭建 Redis集群时需要根据业务数据规模和消息通信成本做出合理规划:
- 在满足业务需要的情况下尽量避免大集群。
- 适度提高cluster-node-timeout降低消息发送频率,同时cluster-node- timeout还影响故障转移的速度,因此需要根据自身业务场景兼顾二者的平衡。
- 如果条件允许集群尽量均匀部署在更多机器上。避免集中部署,集中部署个别机器带宽消耗将非常严重。
Pub/Sub广播问题
在集群模式下所有的publish命令都会向所有的节点进行广播,造成每条publish数据都会在集群内所有节点传播一 次,加重带宽负担。
针对集群模式下publish广播问题,需要引起开发人员注意,当频繁应用 Pub/Sub功能时应该避免在大量节点的集群内使用,否则会严重消耗集群内 网络带宽。针对这种情况建议使用sentinel结构专门用于Pub/Sub功能,从而 规避这一问题。
集群倾斜
集群倾斜指不同节点之间数据量和请求量出现明显差异,这种情况将加 大负载均衡和开发运维的难度。因此需要理解哪些原因会造成集群倾斜,从而避免这一问题。
1、数据倾斜
(1)节点和槽分配严重不均。
(2)不同槽对应键数量差异过大。
(3) 集合对象包含大量元素。
(4)内存相关配置不一致。
2、请求倾斜
集群内特定节点请求量/流量过大将导致节点之间负载不均,影响集群 均衡和运维成本。常出现在热点键场景,当键命令消耗较低时如小对象的 get、set、incr等,即使请求量差异较大一般也不会产生负载严重不均。但是当热点键对应高算法复杂度的命令或者是大对象操作如hgetall、smembers 等,会导致对应节点负载过高的情况。
避免方式如下:
(1)合理设计键,热点大集合对象做拆分或使用hmget替代hgetall避免整 体读取。
(2)不要使用热键作为hash_tag,避免映射到同一槽。
(3)对于一致性要求不高的场景,客户端可使用本地缓存减少热键调用
集群读写分离
1、只读连接
集群模式下从节点不接受任何读写请求,发送过来的键命令会重定向到负责槽的主节点上(其中包括它的主节点)。当需要使用从节点分担主节点读压力时,可以使用readonly
命令打开客户端连接只读状态。之前的复制配置slave-read-only在集群模式下无效。
2、读写分离
集群模式下读写分离成本比较高,可以直接扩展主节点数量提高集群性能,一般不建议集群模式下做读写分离。
集群读写分离有时用于特殊业务场景如:
(1)利用复制的最终一致性使用多个从节点做跨机房部署降低读命令网 络延迟。
(2)主节点故障转移时间过长,业务端把读请求路由给从节点保证读操作可用。
以上场景也可以在不同机房独立部署Redis集群解决,通过客户端多写来维护,读命令直接请求到最近机房的Redis集群,或者当一个集群节点故障时客户端转向另一个集群。
手动故障转移
Redis集群提供了手动故障转移功能:在从节点上执行cluster failover
命令发起转移流程,主从节点角色进行切换,默认情况下转移期间客户端请求会有短暂的阻塞,但不会丢失数据。
手动故障转移的应用场景:
- 主节点迁移:由于从节点默认不响应请求可以安全 下线关闭,但直接下线主节点会导致故障自动转移期间主节点无法对外提供 服务,影响线上业务的稳定性。这时可以使用手动故障转移,把要下线的主 节点安全的替换为从节点后,再做下线操作操作。
- 强制故障转移:当自动故障转移失败时,只要故障的主节点有存活的从节点就可以通过手动转移故障强制让从节点替换故障的主节点,保证集 群的可用性。
自动故障转移失败的场景:
- 主节点和它的所有从节点同时故障。
- 所有从节点与主节点复制断线时间超时,导致从节点被判定为没有故障转移资格。
- 由于网络不稳定等问题,故障发现或故障选举时间无法在规定时间内完成,流程会不断重试,最终从节点复制中断时间超时,失去故障转移资格无法完成转移。
- 集群内超过一半以上的主节点同时故障。
根据以上情况,cluster failover命令提供了两个参force/takeover提供支持:
cluster failover force
——用于当主节点宕机且无法自动完成故障转移情况。cluster failover takeover
——用于集群内超过一半以上主节点故障的场景,请慎用。
手动故障转移时,在满足当前需求的情况下建议优先级:cluster failover
> cluster failover force
> cluster failover takeover
。
数据迁移
应用Redis集群时,常需要把单机Redis数据迁移到集群环境。redis- trib.rb工具提供了导入功能,用于数据从单机向集群环境迁移的场景,但是有很多缺点。
这里推荐一款唯品会开发 的redis-migrate-tool,该工具可满足大多数Redis迁移需求,更多细节见GitHub:https://github.com/vipshop/redis-migrate-tool