Redis 集群进阶之路(上)

PhilemonAntony 发布于4月前 阅读213次
0 条评论

官方地址:https://redis.io/topics/cluster-spec

本文档基于Redis 3.X或更高版本,讲解 Redis 集群算法以及设计原理。此文档长期更新且随着 Redis 新版本特性的变化变动,请留意。

主要特性和设计原理

Redis 集群目标

Redis集群作为 Redis 的一个分布式实现,主要实现以下目标(按重要性排序):

·高性能,以及高达的 1000 个节点的线性可扩展性( linear scalability ),而且是在没有使用代理,异步复制,在值( value )上不执行合并操作的情况下

·写安全( write safety )控制在可接受的范围:当客户端与大多数 master 建立连接, Redis 架构会尽量保持来自客户端的写入请求。其中会存在已确认的写入请求可能会丢失,网络隔离场景下,较小的网络分区中丢失的已确认的写入请求会更大。

·可用性( Availability ):在大部分 master 节点可达,且每个不可达的 master 节点都只少有一个他的 slave 节点可达的情况下, Redis 集群仍能进行正常运行。此外,基于副本迁移功能,当一个 master 节点没有 slave 副本节点时,集群会从有多个 slave 副本节点的 master 迁移一定数量的 slave 节点给它

已实现的部分功能

Redis集群实现了所有 Redis 非分布式版本提供的单键命令,只要键存储在相同节点就可以执行复杂的多键操作命令(像 set 类型的合集或交集的命令)。

Redis集群实现了“哈希标签”的概念,可强制某些键存储在同一节点。但在手动重新绑定期间,多键操作可能不可用,但单键操作始终可用。

Redis集群中只有数据库 0 ( db0 ),且不支持 SELECT 命令去选择数据库,会报错“ (error) ERR SELECT is not allowed in cluster mode ”。

Redis 集群协议中客户端和服务器的角色

Redis集群中,节点负责保存数据、集群状态信息,以及负责将键映射到正确的节点。集群节点能自动发现其他节点,并检查其是否正常,必要时将 slave 提升为 master 。

在执行任务时,集群节点使用TCP总线和二进制协议组成的 Redis 集群总线( Redis Cluster Bus )进行互相连接。节点使用 gossip 协议来传播和集群信息,这样可以:发现新节点、发送 ping 包(用来确保所有节点都处于正常状态),以及在发生特殊情况时发送集群消息。集群总线也用于在集群汇中传播 PUB/SUB 消息,如管理员手动执行故障转移时。

由于集群节点不能代理请求,所以客户端在接收到重定向错误 “ -MOVED ”和“ -ASK ”时,会将命令重定向到其他节点。理论上客户端可自由地向集群中所有节点发送请求,必要时会将请求重定向到其他节点,所以客户端不需要保存集群状态。 不过 客户端可以缓存键值和节点间的映射关系,如此可提高命令执行效率 。

写入的安全性

Redis 集群节点间使用 异步复制 ( asynchronous replication )传输数据,最后一次故障转移执行隐式合并动作( last failover wins implicit merge function )。这意味着最后被选举出来的 master 的数据集最终会覆盖到其他 slave 副本节点。

使用异步复制的缺点就是在故障期间总会丢失一点数据,但连接到大多数master节点的客户端与连接到极少部分 master 节点的客户端情况完全不同。 Redis 集群会尽量保存所有与大多数 master 节点连接的客户端执行的写入,但以下两种情况除外:

1、一个写入操作能到达一个 master 节点,但当 master 节点准备回复客户端时,此写入可能还未通过异步复制到它的 slave 复制节点。若 master 节点在写入还未复制到 slave 节点时挂掉,那么此次写入就会丢失,若 master 节点不可达,就会有一个合适的 slave 被提升为新 master 。

2、理论上另一种写入可能的丢失的情况:

a、网络故障造成网络分区,致使 master 节点不可达

b、故障转移导致 slave 节点被提升为 master

c、 Master 节点再次可达

d、网络分区后 master 节点隔离,使用过时路由信息( out-of-date routing table )的客户端写入数据到旧 master 节点

第二种情况发生的可能性比较小,主要因为master节点不可达一定时间,将不会再接收任何写入请求,且会有其他新 master 替代它,当网络恢复后仍然会在一小段时间内拒绝写入请求,以便其他节点收到配置更新的通知。处于这种状况也需要客户端路由信息还未更新。

通常所有节点都会尝试通过非阻塞连接尝试(non-blocking connection attempt)尽快去访问一个再次加入到集群里的节点,一旦跟该节点建立一个新的连接就会发送一个 ping 包过去(这足够升级节点配置信息)。这保证了一个节点在恢复可写入状态之前先更新知配置信息。 Redis 集群在拥有少数 master 节点和至少一个客户端的分区上容易丢失为数不少的写入操作,这是因为若 master 节点被故障转移到集群中多数节点那边的节点上, 那么所有发送到这些 master 节点的写入操作都会永久性丢失。

一个master节点被故障转移,必须大多数 master 节点至少“ NODE_TIMEOUT ”时间无法访问该节点,所以若网络问题在这段时间内恢复,就不会有写入操作丢失。在网络故障造成网络分区情况下,当分区持续超过“ NODE_TIMEOUT ”时间,集群节点较少的分区的所有写入可能会丢失,但此分区也会禁止客户端的所有写入请求,因此在少数节点所在的分区变得不可用(写)后,会产生一个最大写入损失量(网络分区产生后直至禁止写入时刻),这个量的数据会在网络恢复,旧 master 成为新 master 后丢弃。

可用性

Redis集群在节点数比较少分区的不可用。假设集群多数节点所在分区里有大多数可达的 master 节点,且对于每个不可达的 master 都至少有一个 slave 节点可达,在经过“ NODE_TIMEOUT ”时间后,就会有合适的 slave 节点被选举出来然后故障转移掉他的 master 节点,此时集群会再次可用(故障转移通常在 1~2s 内完成)。

由此可看出 Redis 集群只能容忍少数节点故障,对于大面积网络故障甚至造成网络分区的情况来说( the event of large net splits ), Redis 并不能提供很好的可用性 。如,一个集群由 N 个 master 节点组成,每个 master 都有一个 slave 复制节点。当有一个节点因为网络问题被隔离出去,多数节点所在分区仍然能可用。当两个节点在相同问题下被隔离出去集群仍可用过的概率是 1-(1/(N*2-1)) (在第一个节点故障后还剩下 N*2-1 个节点,那么失去 slave 节点只剩 master 节点的出错的概率是 1/(N*2-1) )

如一个集群有5个 master 节点,每个节点都只有一个 slave 节点,那么在两个节点被隔离分割出去集群不再可用的概率是 1/(5*2-1) = 0.1111 ,即约 11% 的概率。

Redis 集群的副本迁移功能很好的降低了这个概率,副本迁移的主要功能就是避免孤立的 master 出现,或者尽量保证每个 master 都一个 slave 复制节点。因此每当有孤立的 master 出现, redis 集群的副本迁移机制就会启动。但若一组 master 和 slave 节点同时全部故障,副本迁移就没用了,集群也会有部分数据不可用,或者集群直接不可用。因此,在资源足够的情况下,尽量将 master 和 slave 部署在不同的虚拟机,甚至物理机,数据中心等等 。

性能

Redis 集群中节点并不是把命令转发到管理所给出键值的正确的节点上,而是把客户端重定向到服务一定范围内键值的节点上。最终客户端获得一份最新的集群路由表,记录哪些节点服务哪些范围的键,因此在正常操作中客户端是直接连接到对应的节点来发送指令 。

Redis使用异步复制,节点不会等待其他节点的写入确认(若未使用“ WAIT ”明确指出)

多键指令仅用于相邻的键,不是重新分片,数据是永远不会在节点间移动的。单键操作等普通操作和Redis单实例一样。这意味着由于线性扩展性的设计,在一个拥有 N 个 master 节点的 redis 集群和 Redis 独立实例上执行相同的操作,前者的性能将是 Redis 单实例的 N 倍。但请求总是能从客户端到达服务器,并从服务器返回数据回复客户端,且客户端会与节点保持长连接,所以延迟问题两者一样。

非常高的性能和可扩展性,同时保持弱但合理的数据安全性和可用性是Redis集群的主要目标。

为什么要避免使用合并操作

Redis集群设计原理是避免在多个节点中存在同个键值对导致冲突,但这并不总是理想的。 Redis 中的值通常比较大,列表或有序集合中存储数以万计的元素是比较常见的。数据类型的语义也很复杂。传输和合并这类值可能会形成架构主要瓶颈,另外可能需要应用层使用大量的逻辑,以及额外的内存用来存储元数据等等。

There are no strict technological limits here. CRDTs or synchronously replicated state machines can model complex data types similar to Redis. However, the actual run time behavior of such systems would not be similar to Redis Cluster. Redis Cluster was designed in order to cover the exact use cases of the non-clustered Redis version.

Redis 集群主要组件概述

键分布模型( Keys distribution model )

键空间( key space )被切割成 16384 个哈希槽,每个哈希槽存在于一个 master 节点,那么一个集群最多有 16384 个 master 节点,但是 一个集群建议的最大 master 节点数是 1000 个

集群中每个master负责 16384 个哈希槽中的一小部分。当集群没有重新配置(哈希槽从一个节点移动到另一节点)时,集群是稳定的。当集群处于稳定状态,一个哈希槽只被一个 master 节点负责,但 master 节点可以有一个或多个 slave 复制节点,可以在 master 故障时替换之,且这样可以用来水平扩展读操作(这些读操作不要求实时数据))。

用于将键映射到哈希槽的是本算法如下(下一段落,除了哈希标签以外就是按照这个规则):

HASH_SLOT = CRC16(key) mod 16384

其中,CRC16的定义如下:

·名称: XMODEM (也可以称为 ZMODEM 或 CRC-16/ACORN )

·输出长度: 16 bit

·多项数( poly ): 1021 (即是 x16 + x12 + x5 + 1 )

·初始化: 0000

·反射输入字节( Reflect Input byte ): False

·反射输入 CRC ( Reflect Output CRC ): False

·用于输出 CRC 的异或常量( Xor constant to output CRC ): 0000

·该算法对于输入” 123456789 ”的输出: 31C3

CRC16的 16 位输出中的 14 位会被使用(这也是为什么上面的式子中有一个对 16384 取余的操作)。在测试中, CRC16 能相当好地把不同的键均匀地分配到 16384 个槽中。

注意 : 在本文档的附录 A 中有 CRC16 算法的实现。

键哈希标签( Keys hash tags )

为了实现 哈希标签 而使用的 hash 槽计算有一个例外,哈希标签是确保两个键都在同一个哈希槽里的一种方式,主要用来实现集群中多键操作( multi-key operations )。

为了实现哈希标签,在某些条件下,键的哈希槽以另一种不同的方式计算。若键是“ {...} ”模式,那只有“ { ”和“ } ”间的字符串用做哈希计算以获取哈希槽。但同时出现多个“ { ”和“ } ”是可能的,详细计算方法如下:

·当键包含一个“ { ”

·且当“ { ”右边有一个“ } ”

·且当第一次出现和第一次出现“ } ”间有一个或多个字符

然而不是直接计算哈希,而是拿出第一个“ { ”和它右边第一个“ } ” 间的字符串计算哈希值。

示例:

· {user1000}.following 和 {user1000}.followers 这两个键会被哈希到同一个哈希槽,因为只有“ user1000 ”这个子串会被用来计算哈希值

· foo{}{bar} 这个键,“ foo{}{bar} ”整个字符串都被用来计算哈希值,因为第一个出现的“ { ”和它右边第一个出现的“ } ”间没有任何字符

· foo{{bar}}zap 这个键,“ {bar ”这个字符串会被用来计算哈希值,因为它是第一个出现的“ { ”和它右边第一个出现的“ } ”间的字符

· foo{bar}{zap} 这个键,“ bar ”这个字符串会被用来计算哈希值,因为算法会在第一次有效或无效(中间没有任何字符)地匹配到第一个“ { ”和它右边第一个“ } ”时停止

·如此,若一个键是以“ {} ”开头,那么整个键字符会被用来计算哈希值。当使用二进制数据作为键名称时非常有用。

加上哈希标签的特殊处理,下面是用Ruby和 C 语言实现的 HASH_SLOT 函数。

Ruby 样例代码:

def HASH_SLOT(key)

s = key.index "{"

if s

e = key.index "}",s+1

if e && e != s+1

key = key[s+1..e-1]

end

end

crc16(key) % 16384

End

C 样例代码:

unsigned int HASH_SLOT(char *key, int keylen) {

int s, e; /* start-end indexes of { and } */

/* Search the first occurrence of '{'. */

for (s = 0; s < keylen; s++)

if (key[s] == '{') break;

/* No '{' ? Hash the whole key. This is the base case. */

if (s == keylen) return crc16(key,keylen) & 16383;

/* '{' found? Check if we have the corresponding '}'. */

for (e = s+1; e < keylen; e++)

if (key[e] == '}') break;

/* No '}' or nothing between {} ? Hash the whole key. */

if (e == keylen || e == s+1) return crc16(key,keylen) & 16383;

/* If we are here there is both a { and a } on its right. Hash

* what is in the middle between { and }. */

return crc16(key+s+1,e-s-1) & 16383;

}

集群节点属性

每个集群节点都有唯一名称,节点ID(名称)是一个十六进制表示的 160bit 随机数进制,这个随机数是节点第一次启动时生成的(通常是用 /dev/urandom )。节点会把 ID 保存在配置文件,只要节点没被管理员删掉,就会一直使用此 ID 。

“ CLUSTER RESET ”可强制重置节点 ID 。

节点ID在集群中代表着节点身份,节点改变 IP 连接信息,不需要更新节点 ID 。集群能检测到连接信息的变化,然后使用在集群上通信的 gossip 协议发布广播消息,通知配置变更。

节点ID不仅是关联节点的所需信息,在整个 Redis 集群也是全局唯一的。每个节点也会保存一些关联信息,像具体集群节点的配置详情(此配置在集群中保证最终一致性),还有其他信息,如节点最后 ping 的时间,此时间都是节点本地时间。

每个节点都维护其他节点的信息:节点ID,节点的 IP 和端口,一组标志,若标识为 slave 对应的 master 节点,上一次发送 ping 包的时间,上一次收到 pong 包的时间,节点配置版本号,链路状态和节点负责的哈希范围等。

使用 CLUSTER NODES (关于所有字段的详细说明)命令可以获得以上的一些信息,这个命令可以发送到集群中的所有节点。以下示例是在一个只有4个节点的小集群中发送 CLUSTER NODES 命令到一个 master 节点得到的输出:

[root@hd4 7003]# redis-cli -p 7000 cluster nodes

a466e88499423858c5f53de9be640500d9fb3e5b 127.0.0.1:7000@17000 myself,master - 0 1529050482000 7 connected 2365-5961 10923-11421

5055b631a9b310417fa75948a5e473e2e2e1cfee 127.0.0.1:7001@17001 master - 0 1529050484054 12 connected 1616-1990 7202-10922

d1ce7d9db6086c41f13ef0ce3753f82a3bfc420f 127.0.0.1:7002@17002 master - 0 1529050484054 11 connected 1991-2364 12662-16383

bde6fc14465ecdbc71c6630edb5f9a3ab0c45cf0 127.0.0.1:7006@17006 master - 0 1529050484000 9 connected 0-1615 5962-7201 11422-12661

在上面罗列出来的信息中,各个域依次表示的是:节点ID, IP 地址:端口号,标识信息,上一次发送 ping 包的时间,上一次收到 pong 包的时间,配置文件版本号,连接状态,节点负责的哈希槽范围

集群总线

每个Redis集群节点使用一个额外的 TCP 端口接收来自其他节点的传入连接。一般来说此端口比客户端(如 redis-cli )端口大 10000 ,如客户端端口是 6379 ,集群总线端口则是 16379 。在配置端口时,只需指明客户端端口即可。

集群拓扑图

集群是一个网状结构,每个节点使用 TCP 协议连接到其他节点。 N 个节点的集群中,每个节点都有 N-1 个对外的 TCP 连接和 N-1 个对内连接。这些连接一经创建就会 永久保持 。当节点在集群中等待 ping 响应,足够长时间后在标记某节点不可达前,都将尝试刷新与该节点的最近通信时间。 Redis 集群节点间形成网状结构后,节点使用 gossip 协议和配置更新机制,避免节点间在正常情况下交换过多消息,因此交换的消息数量不会以指数形式增长的。

节点间的握手

节点间通过集群总线TCP端口连接,也通过该端口进行 ping 的发送与接收(无论 ping 的发起者是否可信)。但是若消息发送来源不被认为是集群的一部分,则所有其他的包将会被集群节点丢弃。

只在两种情况下,一个节点会认为另一节点是集群的一部分:

·若一个节点使用 MEET 消息广播自己。 MEET 消息和 PING 消息完全一样,但它会强制让接收者接受自己为集群中的一部分。只有管理员使用以下命令请求时,节点才会发送 MEET 消息给其他节点:

CLUSTER MEET IP PORT

·一个已经被信任的节点能通过传播 gossip 消息让另一节点被注册为集群中的一部分。即 A 知道 B , B 知道 C ,最终 B 将发送 gossip 消息“知道 C ”给 A ,接着 A 收到后会把 C 注册为网络拓扑中的一部分,并尝试直接连接 C 。这意味着只要在网络拓扑中指定任意节点接入新节点,最终它们会自动完全连通。即集集群节点能自动发现其他节点,但只有系统管理员强制指定信任关系才能实现。这种机制能防止不同的 Redis 集群因为 IP 地址变更或者其他网络事件而意外混合起来,从而使得集群更加健壮,以及可用性更强。

重定向和重新分片

MOVED 重定向

客户端可以自由地向集群每个节点(包括slave节点)发送查询请求。接收请求的节点会分析请求,若命令是可以执行的(即单键查询,或者同一哈希槽的多键查询),则节点将找出这些 / 个键是哪个节点的哪个哈希槽所服务的。

若哈希槽在这个节点上,那么这个请求执行就非常简单,否则将查看节点上哈希槽与节点的映射表,且返回客户端MOVED错误:

127.0.0.1:7000> get  foo856761

(error) MOVED 87 127.0.0.1:7006

错误信息中指明了存储键的哈希槽编号,以及能处理此查询的IP:PORT。客户端需要重新发送请求到该 IP:PORT 。注意:只要查询的键不在命令接收节点上,都会有与 MOVED 相关的报错。

集群中节点是以ID来标识的,为简化接口,所以只向客户端返回哈希槽和 IP:PORT 之间的映射关系

尽管没有要求,但客户端应该记住哈希槽87被 127.0.0.1:7006 服务,如此一旦有新的命令需发送,它能计算所处目标键的哈希槽,提高找到正确节点的概率。

另一种方法是在收到MOVED重定向时,使用“ CLUSTER NODES ”或“ CLUSTER SLOTS ”命令刷新客户端连接的节点。遇到重定向时,可能多个哈希槽需要重新配置,而不是一个,因此最好是尽快刷新客户端配置信息。

注意:在集群稳定期间(配置没有持续变化),所有客户端将获得一份“哈希槽 -> 节点”的映射表,以提升集群的请求效率,而不用重定向或代理,减少错误。

另外,客户端也要能处理后文将提到的-ASK重定向错误,否则此客户端是不完整的集群客户端。

集群在线重配置( Cluster live reconfiguration )

Redis 集群支持在线添加或删除节点。实际上, 添加和删除被抽象为同一操作,也就是把哈希槽从一个节点迁移到另一节点 。这意味着可以使用相同的机制去平衡集群哈希槽,增加 / 删除节点等。

·向集群添加新节点就是把一个空节点添加到集群,然后从现有节点(可以是多个节点,也可以是单个节点)迁移部分哈希槽到该节点上。

·删除集群节点就是将该节点的哈希槽迁移到其他现有节点

·平衡集群就是将一组哈希槽在节点间移动。

从实际角度看,哈希槽就是一堆键,所以Redis集群在重新分片( reshard )时做的就是把键从一个节点移动到另一节点。

为了理解这是怎么工作的,我们需要理解用来操作redis集群节点上哈希曹转换表( slots translation table )的 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 节点指派或移除哈希槽。指派一个哈希槽意味着告诉目标 master ,它将负责这个 / 些哈希槽的存储与服务。在哈希槽被指派后,节点会将这个消息通过 gossip 协议想整个集群传播。(协议在后续“哈希槽配置传播”章节说明)

ADDSLOTS子命令通常用于新建立 Redis 集群时用于给所有 master 节点指派 16384 个哈希槽中的部分。

DELSLOSTS子命令主要在手工配置集群或调试,实际中用的少。

SETSLOT子命令使用“ SETSLOT <slot> NODE ”形式将哈希槽指派到指定 ID 的节点。此外哈希槽还能设置为两种特殊状态: MIGRATING and IMPORTING 。这两种状态用于将哈希槽从一个节点迁移到另一节点。

·当一个槽被设置为 MIGRATING ,服务该哈希槽的节点会接受所有查询这个哈希槽的请求,但仅当查询的键还存在原节点上时,原节点会处理该请求,否则此查询会通过一个 -ASK 重定向( -ASK redirection )转发到迁移的目标节点上

·当一个槽被设置为 IMPORTING ,仅当接受到 ASKING 命令后节点才会接受所有查询这个哈希槽的请求。若客户端一直未发送 ASKING 命令,那么查询会通过 -MOVED 重定向错误转发到真正处理这个哈希槽的节点上。

以下实例来解释上述。假设有两个master节点 A 、 B 。目的是将哈希槽 8slave 节点 A 移动到节点 B ,因此发送命令:

1、在节点 B 执行: CLUSTER SETSLOT 8 IMPORTING A

2、在节点 A 执行: CLUSTER SETSLOT 8 MIGRATING B

其他所有节点在每次请求的一个键是属于哈希槽8是,都会把客户端指向节点 A :

·所有关于已存在的键的查询都由节点 A 处理

·所有关于不存在于节点 A 的键都由节点 B 处理,因为节点 A 将重定向客户端到节点 B

这种方式让我们可以不用再节点A中创建新的键。另外用于集群配置的“ redis-trib ”脚本也可以把已存在的属于哈希槽 8 的键 slave 节点 A 移动到节点 B 。可通过命令实现:

CLUSTER GETKEYSINSLOT slot count

上述命令会返回指定的哈希槽中count个键。对于每个返回的键, redis-trib 向节点 A 发送一个“ MIGRATE ”命令,该命令将以原子性的方式把指定的键 slave 节点 A 移动到节点 B 。以下是 MIGRATE 的工作原理:

MIGRATE target_host target_port key target_database id timeout

执行MIGRATE命令的节点会连接目标节点,将序列化后的键发送过去,一旦收到“ OK ”回复就会将自己数据集中的老键( old key )删除。因此对于一个外部客户端而言,一个键要么存在于节点 A 要么节点 B 。

Redis集群中仅有一个数据库( db0 ),不能切换到其他数据库,但 MIGRATE 命令能用于其他与 Redis 集群无关的任务。如迁移比较复杂的键像长列表,都被优化的非常快速。但在调整一个拥有很多键,且键的数据量都很大的集群时,若使用它的应用程序有延时问题的限制,再使用此命令就不明智了。

当最终迁移完成,在集群中所有节点上执行命令 “ SETSLOT <slot> NODE <node-id> ”以便将槽设置为正常状态 。

ASK 重定向

前面有提到关于 ASK 重定向( ASK redirection ),那么为啥不直接使用 MOVED 重定向呢?因为当使用 MOVED 时,意味着将哈希槽永久地迁移到另一节点,且希望接下来的所有查询都发到这个指定节点上去。而 ASK 意味着只要下一个查询发送到指定节点上去 。

这个命令是必要的,因为下一个关于哈希槽8的查询需要的键或许还在节点 A 中,因此我们希望客户端尝试在节点 A 中查找,若需要的话也在节点 B 中查找。由于这是发生在 16384 个槽的其中一个槽,所以对于集群的性能影响是在可接受的范围。

然而我们需要强制客户端的行为,以确保客户端会在尝试节点A中查找后去尝试在节点 B 中查找。若客户端在发送查询前发送了 ASKING 命令,那么节点 B 只会接受被设为 IMPORTING 的槽的查询。 本质上来说, ASKING 命令在客户端设置了一个一次性标识( one-time flag ),强制一个节点可以执行一次关于带有 IMPORTING 状态的槽的查询。

因此从客户端角度看,ASK重定向的完整语义如下:

·若接收到 ASK 重定向,那么把查询的对象调整为指定的节点。

·先发送 ASKING 命令,再开始发送查询。

·现在不要更新本地客户端的映射表把哈希槽 8 映射到节点 B 。

一旦完成了哈希槽8的转移,节点 A 会发送一个 MOVED 消息,客户端会把哈希槽 8 映射到新的 IP:PORT 上。注意,即使客户端出现 bug ,过早地执行这个映射更新,也是没有问题的,因为它不会在查询前发送 ASKING 命令,节点 B 会用 MOVED 重定向错误把客户端重定向到节点 A 上。

CLUSTER SETSLOT命令文档中,哈希槽迁移以类似的术语进行解释,但使用不同的措辞(为了文档中的冗余)。

客户端首次连接和处理重定向

尽管有些Redis集群客户端可能不在内存中哈希槽编号与服务节点地址间的映射列表,且只能通过连接到随机节点然后在需要时进行客户端连接的重定向。

实现的客户端应该保存哈希槽编号与服务节点地址间的映射列表,此信息也不要求是最新的,因为请求时若连接到的节点无法获取所需数据,会重定向到其他节点,此时会触发客户端更新映射信息。

客户端通常在两种情况下获取哈希槽与节点映射列表:

·启动时保存初始化的列表信息

·当收到 MOVED 重定向

请注意:客户端可能根据MOVED重定向更新变动的哈希槽,但这种方法仅适用于一个哈希槽更新,当遇到大量哈希槽映射信息更新,如 slave 被提升为 master ,所有映射到旧 master 的哈希槽会重新映射到新 master 。因此更合适的对 MOVED 重定向做出回应的方法是:重新获取哈希槽节点的映射信息

为了获取哈希槽与节点的映射信息,集群提供了一种命令“ CLUSTER NODES ”的替代方案,且此方案只提供客户端需要的信息。这个命令是“ CLUSTER SLOTS ”,命令显示每组哈希槽范围是由哪个 master 、 slave 节点服务的。以下是“ CLUSTER SLOTS ”输出实例

[root@hd4 ~]# redis-cli -c -p 7000 cluster slots

1) 1) (integer) 2365

2) (integer) 5961

3) 1) "127.0.0.1"

2) (integer) 7000

3) "a466e88499423858c5f53de9be640500d9fb3e5b"

4) 1) "127.0.0.1"

2) (integer) 7008

3) "948addb812fe9322a25fbbdac9de940bab09f9f7"

2) 1) (integer) 10923

2) (integer) 11421

3) 1) "127.0.0.1"

2) (integer) 7000

3) "a466e88499423858c5f53de9be640500d9fb3e5b"

4) 1) "127.0.0.1"

2) (integer) 7008

3) "948addb812fe9322a25fbbdac9de940bab09f9f7"

3) 1) (integer) 1616

2) (integer) 1990

3) 1) "127.0.0.1"

2) (integer) 7001

3) "5055b631a9b310417fa75948a5e473e2e2e1cfee"

4) 1) "127.0.0.1"

2) (integer) 7005

3) "406bda57ed591c2bd3b15955f687a57b03a653c0"

5) 1) "127.0.0.1"

2) (integer) 7003

3) "9b1d9c3e7bbcc955afce649f439cd2d094957313"

4) 1) (integer) 7202

2) (integer) 10922

3) 1) "127.0.0.1"

2) (integer) 7001

3) "5055b631a9b310417fa75948a5e473e2e2e1cfee"

4) 1) "127.0.0.1"

2) (integer) 7005

3) "406bda57ed591c2bd3b15955f687a57b03a653c0"

5) 1) "127.0.0.1"

2) (integer) 7003

3) "9b1d9c3e7bbcc955afce649f439cd2d094957313"

5) 1) (integer) 1991

2) (integer) 2364

3) 1) "127.0.0.1"

2) (integer) 7002

3) "d1ce7d9db6086c41f13ef0ce3753f82a3bfc420f"

4) 1) "127.0.0.1"

2) (integer) 7004

3) "b36883be3b39692f71a441a67277ab23dff80afb"

5) 1) "127.0.0.1"

2) (integer) 7009

3) "5837a7c77a04b5100222dca1d226e4980764a97f"

6) 1) (integer) 12662

2) (integer) 16383

3) 1) "127.0.0.1"

2) (integer) 7002

3) "d1ce7d9db6086c41f13ef0ce3753f82a3bfc420f"

4) 1) "127.0.0.1"

2) (integer) 7004

3) "b36883be3b39692f71a441a67277ab23dff80afb"

5) 1) "127.0.0.1"

2) (integer) 7009

3) "5837a7c77a04b5100222dca1d226e4980764a97f"

7) 1) (integer) 0

2) (integer) 1615

3) 1) "127.0.0.1"

2) (integer) 7006

3) "bde6fc14465ecdbc71c6630edb5f9a3ab0c45cf0"

4) 1) "127.0.0.1"

2) (integer) 7007

3) "382b8977ccb4523495bed7ebdbab866f5ada4930"

8) 1) (integer) 5962

2) (integer) 7201

3) 1) "127.0.0.1"

2) (integer) 7006

3) "bde6fc14465ecdbc71c6630edb5f9a3ab0c45cf0"

4) 1) "127.0.0.1"

2) (integer) 7007

3) "382b8977ccb4523495bed7ebdbab866f5ada4930"

9) 1) (integer) 11422

2) (integer) 12661

3) 1) "127.0.0.1"

2) (integer) 7006

3) "bde6fc14465ecdbc71c6630edb5f9a3ab0c45cf0"

4) 1) "127.0.0.1"

2) (integer) 7007

3) "382b8977ccb4523495bed7ebdbab866f5ada4930"

返回数组的每个元素的前两个子元素是该范围的始末哈希槽。附加元素表示地址端口对( address-port pairs )。 第一个地址端口对是服务该范围哈希槽的 master 节点,以下的都是该 master 节点的正处于正常状态的 slave 复制节点 。

如输出的第一个元素表示,槽从2365至 5961 (开始和结束哈希槽)由 127.0.0.1:7000 服务,且可通过 127.0.0.1:7008 水平扩展读负载。

集群配置不正确时,CLUSTER SLOTS不能保证返回的哈希槽范围覆盖 16384 个哈希槽,因此客户初始化哈希槽信息时,若用户执行有关键的命令属于未分配的哈希槽,应当用 NULL 填充空节点,并报告一个错误。

当一个哈希槽被发现未被分配键,返回一个错误给客户端前,客户端应尝试获取哈希槽映射信息,已检查集群配置是否正确。

多键操作

客户端可通过哈希标签任意进行多键操作。如以下有效操作:

MSET {user:1000}.name Angela {user:1000}.surname White

当键所属哈希槽正在进行重新分片时,多键操作可能不可用。详细的说,在重新分片期间,针对相同节点(源节点和目标节点)所有已存在键的多键操作是可用的。

在重新分片时,操作的键不存在或键在源节点和目的节点之间,将产生-TRYAGAIN错误。客户端可以一段时间后再尝试操作,或报错( Operations on keys that don't exist or are - during the resharding - split between the source and destination nodes, will generate a -TRYAGAIN error )

一旦指定的哈希槽的迁移操作结束,所有多键操作可再次用于该哈希槽。

使用 slave 节点扩展读取功能

一般slave节点会将客户端重定向到给定命令中涉及的哈希槽的 master 节点上,但客户端也可使用命令“ READONLY ”在 slave 节点上扩展读性能。命令“ READONLY ”告知 slave 节点,允许不在乎数据是否是最新的、没有写请求的客户端连接它

当连接处于只读模式,请求涉及到不是slave的 master 节点服务的键时,集群将发送一个重定向到客户端。发生这种情况可能是因为:

·客户端发送了一个关于哈希槽的命令,但该哈希槽并不是由这个 slave 的 master 节点提供服务

·集群哈希槽经过重新分配, slave 节点不再为给定哈希槽提供服务

当发生这些情况,客户端应更新哈希槽与节点的映射表。

总言之,“ READONLY ”设置 slave 的只读模式,连接的只读状态能用命令“ READWRITE ”清除

查看原文: Redis 集群进阶之路(上)

  • greenbear
  • orangesnake
  • crazygorilla
  • yellowfrog
  • bigpeacock
  • blackduck
  • smallrabbit
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。