目录
1.前言
2.扩容集群
3.收缩集群
4.客户端路由
5.ASK与MOVED区别
6.对应的指令详解
6.1 cluster meet
6.2 cluster setslot
6.2.1 MIGRATING子命令
6.2.2 IMPORTING子命令
6.2.3 STABLE
6.3.4 NODE
6.3 cluster getkeysinslot
6.4 migrate
6.5 cluster forget
7.参考资料
通过上一章节,我们知道了Redis Cluster采用的是虚拟哈希槽分区,一共分为0-16383个槽位,数据分别落在这些不同的槽位上;
假如,目前集群有N台服务器,这N台服务器加起来维护着0-16383个槽位,每一台机器负责维护一部分槽以及槽所映射的键值数据,槽是Redis集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动:
①当我加1台机器,该集群变成(N+1)台机器,此刻 需要把一些槽位移动到这个新增的机器上;
②当我减1台机器,该集群变成(N-1)台机器,此刻需要把该机器上的槽位移动到其他机器上;
当一个新节点Master4加入现有集群后,我们需要把Master1、Master2、Master3上的一些槽位进行迁移,确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据均匀,为此新节点Master4指定槽的迁移计划,整个迁移计划可以分为2部分:槽位迁移、子节点列表更新,具体步骤如下:
第一步:启动Master4节点,记为M4;
第二步:使用cluster meet命令,让M4节点加入到集群中。新节点刚开始都是主节点状态,由于没有负责的槽,所以不能接受任何读写操作,后续需要为其迁移槽和填充数据;
第三步:对M4节点发送cluster setslot {slot} importing {sourceNodeId}命令,让目标节点M4准备导入槽的数据;
第四步:对源节点Master1、Master2、Master3节点发送cluster setslot {slot} migrating {targetNodeId}命令,让源节点准备迁出槽的数据;
第五步:源节点Master1、Master2、Master3执行cluster getkeysinslot {slot} {count}命令,获取count个属于槽{slot} 的键;
第六步:在源节点Master1、Master2、Master3上执行migrate {targetNodeIp} " " 0 {timeout} keys {key...}命令,把获取的key通过pipeline机制批量迁移到目标节点;
第七步:重复执行第5-6步,直到槽下所有的键值数据迁移到目标节点;
第八步:向集群内所有主节点发送cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点了,往后从该节点那对应槽位的数据,同时为了保证槽节点映射变更及时传播,需要遍历发送给所有主节点,以便更新被迁移的槽执行新节点;
收缩节点就是将某个节点下线,整个流程分为2部分:该节点槽位转移到其他节点、更新节点列表把该节点关闭,具体步骤如下:
槽位转移到其他节点,与《扩容集群》一致,区别在于当迁移完槽后,还需要通知集群内所有节点忘记下线的节点,即Redis集群使用cluster forget {downNodeId}命令来讲指定的节点加入到禁用列表中,在禁用列表内的节点不再发送Gossip消息;
在集群模式下,Redis节点接收任何键相关命令时首先计算键对应的槽,在根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重定向。
Redis计算槽时并非只简单的计算键值内容,当键值内容包括大括号时,则只计算括号内的内容。比如说,key 为 user:{10000}:books时,计算哈希值只计算10000。
由于请求重定向会增加IO开销,必须借助Smart集群客户端。Smart客户端通过在内部维护slot到Redis节点的映射关系,本地就可以实现键到节点的查找,从而保证IO效率的最大化,而MOVED重定向负责协助客户端更新映射关系;
ASK和MOVED虽然都是对客户端的重定向控制,但是有着本质区别:
ASK重定向说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只能是临时性的重定向,客户端不会更新 slot到Redis节点的映射缓存;
MOVED重定向说明键对应的槽已经明确指定到新的节点,因此需要更新slot到Redis节点的映射缓存;
CLUSTER MEET命令被用来连接不同的、开启集群支持的Redis节点,以进入工作集群;(通俗来讲就是新节点加入已经搭建好的集群)
由于每个节点默认都是相互不信任的,且被认为是未知的节点,这是因为即便某一天人为失误配置IP错误,也不会把N个不同集群节点混成一个集群,因为节点与节点之间默认是互不信任与未知的;
为了使特定节点加入到其他节点组成的Redis Cluster节点列表中,这里只有2种方法:
①系统管理员发送一个CLUSTER MEET命令强制一个节点去会面另一个节点;
②一个已知的节点发送一个保存在gossip部分的节点列表,包含着未知的节点。如果接收的节点已经将发送节点信任为已知节点,它会处理gossip部分并且发送一个握手消息给未知的节点。
思考:Redis Cluster需要形成一个完整的网络,即每个节点与其他任何一个节点都会建立连接,那是不是意味着新节点要对其他所有节点都发送一次CLUSTER MEET命令呢?
答:不需要,假如A-B-C-D之间已经建立了节点链,当新节点E通过CLUSTER MEET命令与D建立连接后,相当于暂时存在A-B-C-D、D-E这2个节点链,然后由于在心跳包中会交换gossip信息,因此A-E、B-E、C-E将会自动创建节点链;
该命令、及其子命令可以用来启动、结束一个群集重新分配哈希槽的操作,操作期间,会把源节点对应槽设置为迁移中状态,目标节点设置为导入中状态,子命令可以修改接受节点中哈希槽的状态,具体如下:
①MIGRATING:将一个哈希槽设置为migrating 状态;
②IMPORTING:将一个哈希槽设置为importing 状态;
③STABLE:从哈希槽中清除导入和迁移状态;
④NODE:将一个哈希槽绑定到另一个不同的节点;
CLUSTER SETSLOT
该子命令将一个槽设置为migrating状态.为了可以将一个哈希槽设置成这种状态,收到命令的节点必须是该哈希槽的所有者,否则报错,当一个哈希槽被设置为migrating状态,节点将会有如下操作:
①客户端发来的操作指令中的key如果存在,则命令正常执行;
②客户端发来的操作指令中的key如果不存在,则接收命令的节点将发出一个重定向ASK,让客户端紧在destination-node重试该查询。在这种情况下,客户端不应该将该哈希槽更新为节点映射;
③如果客户端发来的命令中包含多个keys,如果都不存在,处理方式同2;如果都存在,处理方式同1;如果只是部分存在,针对即将完成迁移至目标节点的keys按序返回TRYAGAIN错误,以便批量keys命令可以执行;
CLUSTER SETSLOT
该子命令将keys从指定源节点导入目标节点,该命令仅能在目标节点生效,当一个槽被设置为导入中状态时,该节点的变动情况如下:
客户端发过来的指令中,凡是涉及该哈希槽的命令均被拒绝,并产生一个MOVED重定向,但是如果客户端命令后面紧跟着一个ASKING命令时,目标节点就必须执行,这是因为:
第一步:客户端往源节点发送指令,源节点在执行指令时,发现key不存在了,然后源节点给客户端返回一个ASK指令,或者,客户端直接给目标节点发送指令,目标节点给客户端返回一个MOVED指令,然后客户端收到MOVED指令后去访问源节点,然后源节点再给客户端返回一个ASK指令;
第二步:客户端收到这个ASK指令后,重新发指令发往目标节点,然后紧跟着发送一个ASKING指令;
第三步:当目标节点收到ASKING指令后,这才知道该key已经从源节点移动到我这目标节点了,我不能再回复MOVED指令了,否则客户端-MOVED-源节点-ASK-客户端-目标节点-MOVED-源节点....形成死循环了,看来,我不得不执行该命令了,因此,源节点已不存在的keys或者已经迁移至目标节点的keys的命令,都在目标节点执行,说明如下:
①新的keys总是在目标节点创建。在哈希槽的迁移中,我们只迁移keys不会创建keys;
②涉及已经迁移的keys的命令都会被目的节点处理,目的节点会是新的哈希槽的所有者,以保证一致性;
③如果没有ASKING,则该命令是普通指令,ASKING保证哈希槽映射关系错误的客户端不会在目的节点继续其他操作,例如新建key,毕竟新建key的操作应该在目标节点操作,而不是源节点;
CLUSTER SETSLOT
该子命令仅用于清理槽中迁移中/导入中的状态。它主要用于修复群集在使用redis-trip fix命令后,卡在一个错误状态的场景, 一般情况下,使用SETSLOT... NODE...迁移完成时,这两种状态会被自动清理;
CLUSTER SETSLOT
该子命令使用最复杂,它后接指定节点的哈希槽,该命令仅在特定情况下有效,且不同的槽状态会有不同的效果,前提条件和对应的效果如下:
①如果接受命令的节点是当前操作哈希槽的所有者,但是该命令的操作结果是将操作的槽分配到另一个节点,因此,要操作的哈希槽中还有keys时,该命令会返回错误;
②如果槽是migrating状态,当该槽被分配至其他节点时,migrating状态被清除;
③如果槽在接收命令的节点上是importing状态,该命令将槽分配给这个节点(当进行重哈希时,最终结果哈希槽从一个节点迁移至目的节点)并做如下操作:
首先,状态importing被清除;
其次,如果该节点的配置epoch不是群集中最大的,它将生成一个新的配置epoch。这样,在经历过故障转移或者槽迁移的群集中,能够拿到新的哈希槽的所有权;
CLUSTER GETKEYSINSLOT slot count
本命令返回存储在接受节点的指定hash slot里面的key的列表,key的最大数量通过count参数指定,所以这个API可以用作keys的批处理;
这个命令的主要是用于rehash期间slot从一个节点移动到另外一个节点;
MIGRATE host port key destination-db timeout [COPY] [REPLACE]
将key原子性地从当前实例传送到目标实例的指定数据库上,一旦传送成功,key保证会出现在目标实例上,而当前实例上的key会被删除;
这个命令是一个原子操作,它在执行的时候会阻塞进行迁移的两个实例,直到以下任意结果发生:迁移成功,迁移失败,等到超时;
命令的内部实现是这样的:它在当前实例对给定key执行DUMP 命令 ,将它序列化,然后传送到目标实例,目标实例再使用RESTORE 对数据进行反序列化,并将反序列化所得的数据添加到数据库中;当前实例就像目标实例的客户端那样,只要看到RESTORE命令返回OK,它就会调用DEL删除自己数据库上的key;
timeout以毫秒为单位,指定当前实例与目标实例进行沟通的最大间隔时间。这说明操作并不一定要在timeout毫秒内完成,只是说数据传送的时间不能超过这个timeout数;
MIGRATE命令需要在给定的时间规定内完成IO操作。如果在传送数据时发生IO错误,或者达到了超时时间,那么命令会停止执行,并返回一个特殊的IOERR错误,当IOERR出现时,有以下2种可能:
①key可能存在于两个实例;
②key可能只存在于当前实例;
唯一不可能发生的情况就是丢失key ,因此,如果一个客户端执行MIGRATE命令,且不幸遇上IOERR错误,那么这个客户端唯一要做的就是检查自己数据库上的key是否已经被正确地删除。
如果有其他错误发生,那么MIGRATE保证key只会出现在当前实例中;
CLUSTER FORGET node-id
该命令可以从收到命令的Redis群集节点的节点信息列表中移除指定ID的节点;
该命令不是将待删除节点的信息简单从内部配置中简单删除,它同时实现了禁止列表功能:不允许已删除的节点再次被添加进来,否则已删除节点会因为处理其他节点心跳包中的gossip section时被再次添加,这段话怎么理解呢,举个例子:
因此,在实际情况下,命令执行过程是:
①从收到命令节点的节点信息列表中删除待删除节点的节点信息;
②已删除的节点的节点ID被加入禁止列表,保留1分钟;
③收到命令的节点,在处理从其他节点发送过来的gossip sections会跳过所有在禁止列表中的节点;
Redis Cluster 的数据分片机制:https://cloud.tencent.com/developer/article/1437316
腾讯云:https://cloud.tencent.com/developer/section/1374001
Redis中文官方网站:http://www.redis.cn/
2W字详解Redis 6.0 集群环境搭建实践:https://mp.weixin.qq.com/s/6q_NaQaM2wgc1bgE5cpx0g