去中心Redis-Cluster规范(三)
本文翻译自官方文档
重定向和重哈希
MOVED重定向
Redis客户端可以自由想集群内的任何节点(包括从节点)发送查询指令.收到指令的节点会分析查询指令,如果指令可接受(查询指令中只有一个key,或者多个属于相同hash slot的key)会查找指令中的key隶属的hash slot由哪个节点负责.
如果hash slot由当前节点服务,则查询指令可以被简单的处理掉,否则检查hash slot到节点的映射表,回复客户端一个MOVED错误信息,像下面示例这样.
GET x
-MOVED 3999 127.0.0.1:6381
这个错误信息包含key所属的hash slot(3999)和可以处理该查询指令的服务实例的ip:port.客户端需要重新发起查询到这个指定的节点ip地址和端口.注意如果客户端在重新发起查询之前等待了一段时间,并且同事集群配置发生变更,hash slot 3999现在由另外的节点提供服务,目标节点会再次回复MOVED错误信息.相同的情况也会发生在通信的节点没有收到最新的配置信息时.
从集群的视角看都是使用ID作为节点标识,但是为了简化客户端接口,仅向客户端暴露hash slot到ip:port的映射,从客户端的视角看redis节点是使用ip:port作为标识的.
虽然不强制,但是客户端应该记住hash slot 3999由127.0.0.1:6381提供服务.一旦需要发起一个新命令,客户端能够计算目标key的hash slot,并且有更高的几率选择正确的节点.
另外一个可选方案是在收到MOVED重定向信息时使用CLUSTER NODES
或CLUSTER SLOTS
命令,直接刷新存储在客户端的整个集群布局信息.
当发生重定向时,通常是多个slots被重新配置而不仅仅是一个,所以尽快更新整个客户端的配置信息是最佳策略.
注意当集群稳定时(没有正在执行的配置变更操作),最终所有客户端会获得hash slots -> nodes的映射关系,令集群更高效,因为客户端可以直接定位正确的节点,不需要重定向也没有代理或其他的单点失效问题.
一个完整有效的客户必须处理 -ASK 重定向.文档后面的内容会详细说明.
集群在线重配置
Redis-Cluster支持在运行期间增减节点.添加或移除节点被抽象为相同的操作:从一个节点移动hash slot到另外一个节点.这意味着相同的基本机制可以用来重新平衡集群,添加或删除节点,等等.
- 要向集群添加一个新节点,首先添加一个空节点到集群,然后从已有节点移动一部分hash slot到这个新节点.
- 从集群删除一个节点,需要将分配在这个节点上的hash slot移动到其它已有的节点.
- 重平衡集群就是在集群节点间移动一部分hash slot.
实现的核心是四处移动hash slot的能力.hash slot仅仅是一组key的集合,所以在重哈希期间Redis-Cluster真正要做的就是将key从一个实例移动到另一个实例.移动一个hash slot也就是移动所有分配到这个hash slot中的key.
要理解这是如何工作的,我们需要展示一下在Redis-Cluster中用来维护slots迁移表的CLUSTER
子命令.可以使用如下这些子命令:
- CLUSTER ADDSLOTS slot1 [slot2] … [slotN]
- CLUSTER DELSLOTS slot1 [slot2] … [slotN]
- CLUSTER SETSLOT slot NODE node
- CLUSTER SETSLOT slot MIGRATING node
- CLUSTER SETSLOT slot IMPORTING node
前两个命令 ADDSLOTS和DELSLOTS,可以简单的用来向一个Redis节点添加或移除slots.分配一个slot意味着告知指定的主节点他将要负责存储和服务指定的hash slot内容.
在hash slots被分配之后,他们会使用流言协议传播给整个集群.在后面的配置传播章节中会详细说明.
ADDSLOTS命令通常在从头创建一个新集群时用来为每个主节点分配16384个hash slot中的一部分.
DELSLOTS主要用于人工修改集群配置或出去调试目的:实践中很少使用.
SETSLOT自命令,如果使用SETSLOT \
CLUSTER GETKEYSINSLOT slot count
上面的命令会返回指定的hash slot中的count个key.对每个返回的key,redis-trib发送给节点A一个MIGRATE命令,会以自动的方式将指定的key从A迁移到B(为了在没有竞争的条件下迁移key,两个实例需要同事被锁住一段时间,通常是非常少的时间片).命令示例如下:
MIGRATE target_host target_port key target_database id timeout
MIGRATE 会连接目标实例,发送key的序列化版本,一旦收到了OK,旧key会被从它自己的数据集中删掉.从外部客户端的视角看在任何时间,key要么在A上要么在B上.
在Redis-Cluster中没有必要来指定0意外的数据库(database),但是MIGRATE是个通用命令可能会用于其他非Redis-Cluster环境中的其他任务.即使是在迁移像长列表这样的复杂key时,虽然MIGRATE被优化为尽可能的快,但是如果使用数据库的应用有比较高的延迟限制,在Redis-Cluster中执行含有大key的集群重配置是不明智的.
当迁移过程最终完成后,SETSLOT <slot> NODE <node-id>
命令会被发送到迁移过程涉及到的两个节点来将slots的状态重新设置为正常.相同的命令还会被发送到其它的节点以避免等待正常的集群新配置传播.
ASK重定向
在之前的章节中我们简单的谈到了ASK重定向.为什么我不能简单的使用MOVED重定向?因为MOVED表示我们认为要访问的hash slot永久的由另外的节点提供服务接下来的所有请求应该直接访问指定的节点,ASK表示仅下一次请求访问指定节点.
因为下一次有关hash slot 8的请求可能使用了一个依然在A中的key,所以我们总是想要客户端先尝试访问A如果需要的话再访问B.由于这种情况仅会针对16384个solt中的一个发生,所以对集群的性能影响是可以接受的.
我们需要强制客户端行为,以确保客户端总是在尝试访问A节点之后在尝试访问B节点,如果客户端在发送一个查询请求前先发送了ASKING命令,节点B金辉接受被设置为IMPORTING的slot的查询请求.
本质上来说ASKING命令在客户端设置了一个一次性标记强制一个节点提供对IMPORTING的slot的服务.
从客户端的视角来看,ASK重定向的语义如下:
- 如果接收到了ASK重定向,仅发送一次请求到重定向的指定节点但是接下来的请求依然发送到旧节点.
- 发送被重定向的请求前先发送一个ASKING命令.
- 不要更新客户端的本地映射表,将slot 8 映射到B.
一旦hash slot 8 迁移完成,A将发送MOVED消息,客户端可以永久的将hash slot 8映射到新的ip:port.注意如果客户端有bug,提前设置了这个映射关系也没有问题.因为这个客户端在发起请求前,不会发送ASKING命令,所以B会使用MOVED错误消息将客户端重定向到A.
在 CLUSTER SETSLOT 命令的文档中对Slots迁移也使用了类似的术语来解释了slots迁移.但是为了减少冗余使用了不同的措辞.
客户端首次连接和处理重定向处理
客户端实现可以不在内存中保存slots配置(slot编号和提供服务的节点地址的映射关系)而仅仅通过随机访问一个节点然后等待重定向的方式工作.但是这样的客户端实现是非常低效的.
Redis-Cluster客户端应该足够聪慧可以记住slots配置.这个配置不是必须保持实时更新的.因为访问一个错误节点仅仅会导致简单的重定向,而这个重定向会出发客户端视图的更新.
客户端通常需要在以下两种场景获取一份完整的slots和对应节点地址列表:
- 在启动时初始化slots配置
- 接收到MOVED重定向消息
注意客户端可以接收到MOVED重定向消息时可以只在映射表中更新移动了的slot,然而这样通常是低效的,因为搜歌slots的配置修改经常是同时发生的(例如一个从节点升级为主节点,所有的被就的主节点服务的slots会被重映射).所以收到MOVED重定向后重新获取一份slots到节点的映射关系表是更简单的方案.
为了获取slots配置,除了CLUSTER NODES之外,Redis-Cluster提供了另外一个命令,这个命令不去要解析并且仅仅提供了客户端严格需要的信息.
这个心命令是CLUSTER SLOTS,它提供了一个slots范围的数组和服务这些slot范围的主从节点.
接下来是CLUSTER SLOTS的输出示例:
127.0.0.1:7000> cluster slots
1) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 7001
4) 1) "127.0.0.1"
2) (integer) 7004
2) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 7000
4) 1) "127.0.0.1"
2) (integer) 7003
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7002
4) 1) "127.0.0.1"
2) (integer) 7005
在返回的数组中开头的两个子元素是slots范围的开始和结束.其他附加的元素表示ip:port对.第一个ip:port对是服务这个slot范围的主节点,然后其余的ip:port对是所有服务这个slot范围的从节点(不处于错误状态下,没有被设置FAIL标记).
例如输出中的第一个元素表示slot范围从5461到10922被服务于127.0.0.1:7001,并且可以扩展只读负载到127.0.0.1:7004这个从节点.
如果集群缺少配置,CLUSTER SLOTS不能确保返回的slots范围覆盖了整个16384个slots,所以客户端应该初始化slots配置,使用NULL来填充目标节点,并且当用户试图使用隶属于未分配的slot的key执行命令时需要报告一个错误.
发现一个slot没有被分配而返回错误消息给调用者前,客户端应该再次尝试获取slots配置来检查现在集群是否被正确配置了.
多key操作
使用hash tags,客户端可以自由使用多key操作.如下的操作是有效的:
MSET {user:1000}.name Angela {user:1000}.surname White
当key所属的slot正在进行重哈希时,多key操作可能变为不可用.即使在重哈希进行时,如果所有目标key都存在于相同的节点上(源节点或目标节点),这样的key操作依然可用.
在重哈希进行时,如果操作的key被分隔在源和目标节点上,会生成一个-TRYAGAIN
错误信息.客户端可以过一会儿重试操作,或者直接报错.
一旦指定的hash slot迁移结束,所有在这个slot中的多key操作将重新可用.
使用从节点扩展读能力
对于一个指定的命令,正常情况下从节点会将客户端重定向到牵扯到的slot所在的主节点.然后客户端也可以通过READONLY
命令使用从节点来扩展读能力.
READONLY
告知Redis-Cluster的一个从节点客户端可以接受读到稍旧的数据,且不会执行写操作.
当连接处于readonly模式,集群只会在操作牵扯到的key不在所访问的从节点的主节点上时重定向客户端.原因如下:
- 客户端发送的命令涉及到的slots从来没有存在于该从节点的主节点上.
- 集群被重配置(例如重哈希)且从节点不在服务命令涉及到的hash slot.
当发生上述情况,客户端应该想之前的章节解释的那样,更新自己的hash slot映射关系.
连接的readonly状态可以使用READWRITE清除掉.