
集群伸缩1、伸缩原理2、扩容集群2.1、准备新节点2.2、加入集群2.3、迁移槽和数据3、收缩集群3.1、下线迁移槽3.2、忘记节点1、伸缩原理Redis集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下可以为集群添加节点进行扩容也可以下线部分节点进行缩容如图所示。从图看出Redis集群可以实现对节点的灵活上下线控制。其中原理可抽象为槽和对应数据在不同节点之间灵活移动。首先来看我们之前搭建的集群槽和数据与节点的对应关系如图所示。三个主节点分别维护自己负责的槽和对应的数据如果希望加入1个节点实现集群扩容时需要通过相关命令把一部分槽和数据迁移给新节点如图所示。图中每个节点把一部分槽和数据迁移到新的节点6385每个节点负责的槽和数据相比之前变少了从而达到了集群扩容的目的。这里我们故意忽略了槽和数据在节点之间迁移的细节目的是想让读者重点关注在上层槽和节点分配上来理解集群的水平伸缩的上层原理集群伸缩槽和数据在节点之间的移动下面将介绍集群扩容和收缩的细节。2、扩容集群扩容是分布式存储最常见的需求Redis集群扩容操作可分为如下步骤准备新节点。加入集群。迁移槽和数据。2.1、准备新节点需要提前准备好新节点并运行在集群模式下新节点建议跟集群内的节点配置保持一致便于管理统一。准备好配置后启动两个节点命令如下redis-server conf/redis-6385.conf redis-server conf/redis-6386.conf启动后的新节点作为孤儿节点运行并没有其他节点与之通信集群结构如图所示。2.2、加入集群新节点依然采用cluster meet命令加入到现有集群中。在集群内任意节点执行cluster meet命令让6385和6386节点加入进来命令如下127.0.0.1:6379cluster meet127.0.0.16385127.0.0.1:6379cluster meet127.0.0.16386新节点加入后集群结构如图所示。集群内新旧节点经过一段时间的ping/pong消息通信之后所有节点会发现新节点并将它们的状态保存到本地。例如我们在6380节点上执行cluster nodes命令可以看到新节点信息如下所示127.0.0.1:6380cluster ndoes 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 master -014693478007597connected 475528b1bcf8e74d227104a6cf1bf70f00c24aae127.0.0.1:6386 master -014693477987438connected …新节点刚开始都是主节点状态但是由于没有负责的槽所以不能接受任何读写操作。对于新节点的后续操作我们一般有两种选择为它迁移槽和数据实现扩容。作为其他主节点的从节点负责故障转移。redis-trib.rb工具也实现了为现有集群添加新节点的命令还实现了直接添加为从节点的支持命令如下redis-trib.rb add-node new_host:new_port existing_host:existing_port--slave--master-idarg内部同样采用cluster meet命令实现加入集群功能。对于之前的加入集群操作我们可以采用如下命令实现新节点加入redis-trib.rb add-node127.0.0.1:6385127.0.0.1:6379 redis-trib.rb add-node127.0.0.1:6386127.0.0.1:6379正式环境建议使用redis-trib.rb add-node命令加入新节点该命令内部会执行新节点状态检查如果新节点已经加入其他集群或者包含数据则放弃集群加入操作并打印如下信息[ERR]Node127.0.0.1:6385 is not empty. Either thenodealready knows other nodes(check with CLUSTER NODES)or contains some keyindatabase0.如果我们手动执行cluster meet命令加入已经存在于其他集群的节点会造成被加入节点的集群合并到现有集群的情况从而造成数据丢失和错乱后果非常严重线上谨慎操作。2.3、迁移槽和数据加入集群后需要为新节点迁移槽和相关数据槽在迁移过程中集群可以正常提供读写服务迁移过程是集群扩容最核心的环节下面详细讲解。(1)槽迁移计划槽是Redis集群管理数据的基本单位首先需要为新节点制定槽的迁移计划确定原有节点的哪些槽需要迁移到新节点。迁移计划需要确保每个节点负责相似数量的槽从而保证各节点的数据均匀。例如在集群中加入6385节点如图所示。加入6385节点后原有节点负责的槽数量从6380变为4096个。槽迁移计划确定后开始逐个把槽内数据从源节点迁移到目标节点如图所示。(2)迁移数据数据迁移过程是逐个槽进行的每个槽数据迁移的流程如图所示。流程说明对目标节点发送clustersetslot{slot}importing{sourceNodeId}命令让目标节点准备导入槽的数据。对源节点发送clustersetslot{slot}migrating{targetNodeId}命令让源节点准备迁出槽的数据。源节点循环执行cluster getkeysinslot{slot}{count}命令获取count个属于槽{slot}的键。在源节点上执行migrate{targetIp}{targetPort}0{timeout}keys{keys…}命令把获取的键通过流水线(pipeline)机制批量迁移到目标节点批量迁移版本的migrate命令在Redis3.0.6以上版本提供之前的migrate命令只能单个键迁移。对于大量key的场景批量键迁移将极大降低节点之间网络IO次数。重复执行步骤3)和步骤4)直到槽下所有的键值数据迁移到目标节点。向集群内所有主节点发送clustersetslot{slot}node{targetNodeId}命令通知槽分配给目标节点。为了保证槽节点映射变更及时传播需要遍历发送给所有主节点更新被迁移的槽指向新节点。使用伪代码模拟迁移过程如下defmove_slot(source,target,slot):# 目标节点准备导入槽 target.cluster(setslot,slot,importing,source.nodeId);# 目标节点准备全出槽 source.cluster(setslot,slot,migrating,target.nodeId);whiletrue:# 批量从源节点获取键 keyssource.cluster(getkeysinslot,slot,pipeline_size);ifkeys.length0:# 键列表为空时退出循环break;# 批量迁移键到目标节点 source.call(migrate,target.host,target.port,,0,timeout,keys,keys);# 向集群所有主节点通知槽被分配给目标节点fornode in nodes:ifnode.flagslave:continue;node.cluster(setslot,slot,node,target.nodeId);根据以上流程我们手动使用命令把源节点6379负责的槽4096迁移到目标节点6385中流程如下1)目标节点准备导入槽4096数据127.0.0.1:6385cluster setslot4096importing cfb28ef1deee4e0fa78da86abe5d24566744411e OK确认槽4096导入状态开启127.0.0.1:6385cluster nodes 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 myself,master -007connected[4096--cfb28ef1deee4e0fa78da86abe5d24566744411e]…2)源节点准备导出槽4096数据127.0.0.1:6379cluster setslot4096migrating 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 OK3)批量获取槽4096对应的键这里我们获取到3个处于该槽的键127.0.0.1:6379cluster getkeysinslot40961001)key:test:50282)key:test:682533)key:test:79212确认这三个键是否存在于源节点127.0.0.1:6379mget key:test:5028 key:test:68253 key:test:792121)value:50282)value:682533)value:79212批量迁移这3个键migrate命令保证了每个键迁移过程的原子性127.0.0.1:6379migrate127.0.0.1638505000keys key:test:5028 key:test:68253 key:test:79212出于演示目的我们继续查询这三个键发现已经不在源节点中Redis返回ASK转向错误ASK转向负责引导客户端找到数据所在的节点细节将在后面10.5节“请求路由”中说明。127.0.0.1:6379mget key:test:5028 key:test:68253 key:test:79212(error)ASK4096127.0.0.1:6385通知所有主节点槽4096指派给目标节点6385127.0.0.1:6379cluster setslot4096node1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6380cluster setslot4096node1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6381cluster setslot4096node1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385cluster setslot4096node1a205dd8b2819a00dd1e8b6be40a8e2abe77b756确认源节点6379不再负责槽4096改为目标节点6385负责127.0.0.1:6379cluster nodes cfb28ef1deee4e0fa78da86abe5d24566744411e127.0.0.1:6379 myself,master -000connected0-40954097-5461 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 master -014697180110797connected4096…手动执行命令演示槽迁移过程是为了让读者更好地理解迁移流程实际操作时肯定涉及大量槽并且每个槽对应非常多的键。因此redis-trib提供了槽重分片功能命令如下redis-trib.rb reshard host:port--fromarg--toarg--slotsarg--yes--timeoutarg--pipelinearghostport必传参数集群内任意节点地址用来获取整个集群信息。--from制定源节点的id如果有多个源节点使用逗号分隔如果是all源节点变为集群内所有主节点在迁移过程中提示用户输入。--to需要迁移的目标节点的id目标节点只能填写一个在迁移过程中提示用户输入。--slots需要迁移槽的总数量在迁移过程中提示用户输入。--yes当打印出reshard执行计划时是否需要用户输入yes确认后再执行reshard。--timeout控制每次migrate操作的超时时间默认为60000毫秒。--pipeline控制每次批量迁移键的数量默认为10。reshard命令简化了数据迁移的工作量其内部针对每个槽的数据迁移同样使用之前的流程。我们已经为新节点6395迁移了一个槽4096剩下的槽数据迁移使用redis-trib.rb完成命令如下#redis-trib.rb reshard 127.0.0.1:6379Performing Cluster Check(usingnode127.0.0.1:6379)M: cfb28ef1deee4e0fa78da86abe5d24566744411e127.0.0.1:6379 slots:0-4095,4097-5461(5461slots)master1additional replica(s)M: 40b8d09d44294d2e23c7c768efc8fcd153446746127.0.0.1:6381 slots:10923-16383(5461slots)master1additional replica(s)M: 8e41673d59c9568aa9d29fb174ce733345b3e8f1127.0.0.1:6380 slots:5462-10922(5461slots)master1additional replica(s)M: 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 slots:4096(1slots)master0additional replica(s)// …[OK]All nodes agree about slots configuration.Checkforopenslots…Check slots coverage…[OK]All16384slots covered.打印出集群每个节点信息后reshard命令需要确认迁移的槽数量这里我们输入4096个How many slotsdoyou want to move(from1to16384)4096输入6385的节点ID作为目标节点目标节点只能指定一个What is the receivingnodeID 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756之后输入源节点的ID这里分别输入节点6379、6380、6381三个节点ID最后用done表示结束Please enter all thesourcenodeIDs. Typeallto use all the nodes assourcenodesforthehashslots. Typedoneonce you entered all thesourcenodes IDs. Sourcenode#1:cfb28ef1deee4e0fa78da86abe5d24566744411eSourcenode#2:8e41673d59c9568aa9d29fb174ce733345b3e8f1Sourcenode#3:40b8d09d44294d2e23c7c768efc8fcd153446746Sourcenode#4:done数据迁移之前会打印出所有的槽从源节点到目标节点的计划确认计划无误后输入yes执行迁移工作Moving slot0from cfb28ef1deee4e0fa78da86abe5d24566744411e …. Moving slot1365from cfb28ef1deee4e0fa78da86abe5d24566744411e Moving slot5462from 8e41673d59c9568aa9d29fb174ce733345b3e8f1 … Moving slot6826from 8e41673d59c9568aa9d29fb174ce733345b3e8f1 Moving slot10923from 40b8d09d44294d2e23c7c768efc8fcd153446746 … Moving slot12287from 40b8d09d44294d2e23c7c768efc8fcd153446746 Do you want to proceed with the proposed reshard plan(yes/no)yesredis-trib工具会打印出每个槽迁移的进度如下Moving slot0from127.0.0.1:6379 to127.0.0.1:6385 …. …. Moving slot1365from127.0.0.1:6379 to127.0.0.1:6385..Moving slot5462from127.0.0.1:6380 to127.0.0.1:6385: …. …. Moving slot6826from127.0.0.1:6380 to127.0.0.1:6385..Moving slot10923from127.0.0.1:6381 to127.0.0.1:6385..… Moving slot10923from127.0.0.1:6381 to127.0.0.1:6385..当所有的槽迁移完成后reshard命令自动退出执行cluster nodes命令检查节点和槽映射的变化如下所示127.0.0.1:6379cluster nodes 40622f9e7adc8ebd77fca0de9edfe691cb8a74fb127.0.0.1:6382 slave cfb28ef1deee4e0fa 78da86abe5d24566744411e014697790845183connected 40b8d09d44294d2e23c7c768efc8fcd153446746127.0.0.1:6381 master -014697790855282connected12288-16383 4fa7eac4080f0b667ffeab9b87841da49b84a6e4127.0.0.1:6384 slave 40b8d09d44294d2e2 3c7c768efc8fcd153446746014697790875445connected be9485a6a729fc98c5151374bc30277e89a461d8127.0.0.1:6383 slave 8e41673d59c9568aa 9d29fb174ce733345b3e8f1014697790885524connected cfb28ef1deee4e0fa78da86abe5d24566744411e127.0.0.1:6379 myself,master -000connected1366-40954097-5461 475528b1bcf8e74d227104a6cf1bf70f00c24aae127.0.0.1:6386 master -014697790865368connected 8e41673d59c9568aa9d29fb174ce733345b3e8f1127.0.0.1:6380 master -014697790855281connected6827-10922 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 master -014697790835139connected0-136540965462-682610923-12287节点6385负责的槽变为0-136540965462-682610923-12287。由于槽用于hash运算本身顺序没有意义因此无须强制要求节点负责槽的顺序性。迁移之后建议使用redis-trib.rb rebalance命令检查节点之间槽的均衡性。命令如下# redis-trib.rb rebalance 127.0.0.1:6380Performing Cluster Check(usingnode127.0.0.1:6380)[OK]All nodes agree about slots configuration.Checkforopenslots…Check slots coverage…[OK]All16384slots covered. *** No rebalancing needed!All nodes are within the2.0% threshold.可以看出迁移之后所有主节点负责的槽数量差异在2%以内因此集群节点数据相对均匀无需调整。(3)添加从节点扩容之初我们把6385、6386节点加入到集群节点6385迁移了部分槽和数据作为主节点但相比其他主节点目前还没有从节点因此该节点不具备故障转移的能力。这时需要把节点6386作为6385的从节点从而保证整个集群的高可用。使用cluster replicate{masterNodeId}命令为主节点添加对应从节点注意在集群模式下slaveof添加从节点操作不再支持。如下所示127.0.0.1:6386cluster replicate 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756从节点内部除了对主节点发起全量复制之外还需要更新本地节点的集群相关状态查看节点6386状态确认已经变成6385节点的从节点127.0.0.1:6386cluster nodes 475528b1bcf8e74d227104a6cf1bf70f00c24aae127.0.0.1:6386 myself,slave 1a205dd8b2 819a00dd1e8b6be40a8e2abe77b756008connected 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 master -014697790835139connected0-136540965462-682610923-12287 …到此整个集群扩容完成集群关系结构如图所示。3、收缩集群收缩集群意味着缩减规模需要从现有集群中安全下线部分节点。安全下线节点流程如图所示。流程说明首先需要确定下线节点是否有负责的槽如果是需要把槽迁移到其他节点保证节点下线后整个集群槽节点映射的完整性。当下线节点不再负责槽或者本身是从节点时就可以通知集群内其他节点忘记下线节点当所有的节点忘记该节点后可以正常关闭。3.1、下线迁移槽下线节点需要把自己负责的槽迁移到其他节点原理与之前节点扩容的迁移槽过程一致。例如我们把6381和6384节点下线节点信息如下127.0.0.1:6381cluster nodes 40b8d09d44294d2e23c7c768efc8fcd153446746127.0.0.1:6381 myself,master -002connected12288-16383 4fa7eac4080f0b667ffeab9b87841da49b84a6e4127.0.0.1:6384 slave 40b8d09d44294d2e2 3c7c768efc8fcd153446746014698941807805connected …6381是主节点负责槽(12288-16383)6384是它的从节点如图所示。下线6381之前需要把负责的槽迁移到其他节点。收缩正好和扩容迁移方向相反6381变为源节点其他主节点变为目标节点源节点需要把自身负责的4096个槽均匀地迁移到其他主节点上。这里直接使用redis-trib.rb reshard命令完成槽迁移。由于每次执行reshard命令只能有一个目标节点因此需要执行3次reshard命令分别迁移1365、1365、1366个槽如下所示#redis-trib.rb reshard 127.0.0.1:6381Performing Cluster Check(usingnode127.0.0.1:6381)…[OK]All16384slots covered. How many slotsdoyou want to move(from1to16384)1365What is the receivingnodeID cfb28ef1deee4e0fa78da86abe5d24566744411e /*输入6379 节点id作为目标节点.*/ Please enter all thesourcenodeIDs. Typeallto use all the nodes assourcenodesforthehashslots. Typedoneonce you entered all thesourcenodes IDs. Sourcenode#1:40b8d09d44294d2e23c7c768efc8fcd153446746 /*源节点6381 id*/Sourcenode#2:done /* 输入done确认 */… Do you want to proceed with the proposed reshard plan(yes/no)yes…槽迁移完成后6379节点接管了1365个槽12288~13652如下所示127.0.0.1:6379cluster nodes cfb28ef1deee4e0fa78da86abe5d24566744411e127.0.0.1:6379 myself,master -0010connected1366-40954097-546112288-13652 40b8d09d44294d2e23c7c768efc8fcd153446746127.0.0.1:6381 master -014698957252272connected13653-16383 …继续把1365个槽迁移到节点6380#redis-trib.rb reshard 127.0.0.1:6381Performing Cluster Check(usingnode127.0.0.1:6381)… How many slotsdoyou want to move(from1to16384)1365What is the receivingnodeID 8e41673d59c9568aa9d29fb174ce733345b3e8f1 /*6380节点id 作为目标节点.*/ Please enter all thesourcenodeIDs. Typeallto use all the nodes assourcenodesforthehashslots. Typedoneonce you entered all thesourcenodes IDs. Sourcenode#1:40b8d09d44294d2e23c7c768efc8fcd153446746Sourcenode#2:done… Do you want to proceed with the proposed reshard plan(yes/no)yes …完成后6380节点接管了1365个槽13653~15017如下所示127.0.0.1:6379cluster nodes 40b8d09d44294d2e23c7c768efc8fcd153446746127.0.0.1:6381 master -014698961232952connected15018-16383 8e41673d59c9568aa9d29fb174ce733345b3e8f1127.0.0.1:6380 master -0146989612531111connected6827-1092213653-15017 …把最后的1366个槽迁移到节点6385中如下所示#redis-trib.rb reshard 127.0.0.1:6381… How many slotsdoyou want to move(from1to16384)1366What is the receivingnodeID 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 /*6385 节点id作为目标节点.*/ Please enter all thesourcenodeIDs. Typeallto use all the nodes assourcenodesforthehashslots. Typedoneonce you entered all thesourcenodes IDs. Sourcenode#1:40b8d09d44294d2e23c7c768efc8fcd153446746Sourcenode#2:done… Do you want to proceed with the proposed reshard plan(yes/no)yes…到目前为止节点6381所有的槽全部迁出完成6381不再负责任何槽。状态如下所示127.0.0.1:6379cluster nodes 40b8d09d44294d2e23c7c768efc8fcd153446746127.0.0.1:6381 master -014698964447682connected 8e41673d59c9568aa9d29fb174ce733345b3e8f1127.0.0.1:6380 master -0146989644376011connected6827-1092213653-15017 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 master -0146989644577712connected0-136540965462-682610923-1228715018-16383 cfb28ef1deee4e0fa78da86abe5d24566744411e127.0.0.1:6379 myself,master -0010connected1366-40954097-546112288-13652 be9485a6a729fc98c5151374bc30277e89a461d8127.0.0.1:6383 slave 8e41673d59c9568aa9d29fb17 4ce733345b3e8f10146989644426411connected …下线节点槽迁出完成后剩下的步骤需要让集群忘记该节点。3.2、忘记节点由于集群内的节点不停地通过Gossip消息彼此交换节点状态因此需要通过一种健壮的机制让集群内所有节点忘记下线的节点。也就是说让其他节点不再与要下线节点进行Gossip消息交换。Redis提供了clusterforget{downNodeId}命令实现该功能如图所示。当节点接收到cluster forget{down NodeId}命令后会 把nodeId指定的节点加入到禁用列表中在禁用列表内的节点不再发送Gossip消息。禁用列表有效期是60秒超过60秒节点会再次参与消息交换。也就是说当第一次forget命令发出后我们有60秒的时间让集群内的所有节点忘记下线节点。线上操作不建议直接使用cluster forget命令下线节点需要跟大量节点命令交互实际操作起来过于繁琐并且容易遗漏forget节点。建议使用redis-trib.rb del-node{hostport}{downNodeId}命令内部实现的伪代码如下defdelnode_cluster_cmd(downNode):# 下线节点不允许包含slotsifdownNode.slots.length!0exit1end # 向集群内节点发送cluster forgetforn in nodes:ifn.iddownNode.id:# 不能对自己做forget操作continue;# 如果下线节点有从节点则把从节点指向其他主节点ifn.replicaten.replicate.nodeIddownNode.id:# 指向拥有最少从节点的主节点 masterget_master_with_least_replicas();n.cluster(replicate,master.nodeId);#发送忘记节点命令 n.cluster(forget,downNode.id)# 节点关闭 downNode.shutdown();从伪代码看出del-node命令帮我们实现了安全下线的后续操作。当下线主节点具有从节点时需要把该从节点指向到其他主节点因此对于主从节点都下线的情况建议先下线从节点再下线主节点防止不必要的全量复制。对于6381和6384节点下线操作命令如下redis-trib.rb del-node127.0.0.1:6379 4fa7eac4080f0b667ffeab9b87841da49b84a6e4#从节点6384idredis-trib.rb del-node127.0.0.1:6379 40b8d09d44294d2e23c7c768efc8fcd153446746#主节点6381id节点下线后确认节点状态127.0.0.1:6379cluster nodes cfb28ef1deee4e0fa78da86abe5d24566744411e127.0.0.1:6379 myself,master -0010connected1366-40954097-546112288-13652 be9485a6a729fc98c5151374bc30277e89a461d8127.0.0.1:6383 slave 8e41673d59c9568aa 9d29fb174ce733345b3e8f10147004803562411connected 475528b1bcf8e74d227104a6cf1bf70f00c24aae127.0.0.1:6386 slave 1a205dd8b2819a00d d1e8b6be40a8e2abe77b7560147004803260412connected 40622f9e7adc8ebd77fca0de9edfe691cb8a74fb127.0.0.1:6382 slave cfb28ef1deee4e0fa 78da86abe5d24566744411e0147004803512010connected 8e41673d59c9568aa9d29fb174ce733345b3e8f1127.0.0.1:6380 master -0147004803461711connected6827-1092213653-15017 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756127.0.0.1:6385 master -0147004803361412connected0-136540965462-682610923-1228715018-16383集群节点状态中已经不包含6384和6381节点到目前为止我们完成了节点的安全下线新的集群结构如图所示。本节介绍了Redis集群伸缩的原理和操作方式它是Redis集群化之后最重要的功能熟练掌握集群伸缩技巧后可以针对线上的数据规模和并发量做到从容应对。