redis-cluster重定向和重分片

本文介绍了Redis集群的MOVED重定向、在线重新配置、ASK重定向等内容。Redis客户端可向任意节点发请求,遇MOVED需重发。集群支持在线增减节点,核心是迁移哈希槽。ASK重定向用于临时请求。客户端应记忆哈希槽配置,多键操作在重新分片时有不同情况,还可通过slave节点扩展读。

MOVED 重定向

一个 Redis 客户端可以自由地向集群中的任意节点(包括slave节点)发送请求。接收的节点会分析请求,如果这个命令是集群可以执行的(就是查询中只涉及一个键,或者多键在同一个哈希槽),节点会找出这个键/这些键所属的哈希槽对应的节点。

如果哈希槽在这个节点上,那么这个请求就简单地执行了。否则这个节点会查看它内部的哈希槽节点映射,然后给客户端返回一个MOVED错误,如下:

GET x

-MOVED 3999 127.0.0.1:6381

这个错误包括键的哈希槽和能处理这个查询的节点的IP:端口。客户端需要重新发送请求到给定IP 地址和端口号的节点。 注意:即使客户端在重发请求之前等待了很长一段时间,与此同时集群的配置信息发生改变,如果哈希槽3999现在是归属其他节点,那么目标节点会再向客户端回复一个MOVED错误。如果连接的节点没有信息变更,会重复这样。

从集群的角度看,节点是以 ID来标识的。我们尝试简化接口,所以只向客户端暴露哈希槽和用 IP:端口来标识的Redis节点之间的映射。

虽然并没有要求,但是客户端应该尝试记住哈希槽3999归属于127.0.0.1:6381。这样的话,一旦有一个新的命令需要发送,它能计算出目标键的哈希槽,找到正确节点的机率更高。

另一种方法是,当遇到一个MOVED时,使用CLUSTER NODES 或CLUSTER SLOTS命令刷新整个客户端集群布局。当遇到重定向,很可能多个插槽进行重新配置,而不是只有一个,所以尽快更新客户端的配置往往是最好的策略。

注意,当集群是稳定的时候(配置没有在变更),所有客户端最终都会得到一份哈希槽->节点 的映射表,这样能使得集群效率非常高,客户端直接定位目标节点,不用重定向、代理或发生其他单点故障。

一个客户端也应该能处理后文提到的 -ASK 重定向错误,否则不是一个完整的Redis集群客户端。
 

集群在线重新配置

Redis集群支持在集群运行过程中添加或移除节点。实际上,添加或移除节点都被抽象为同一个操作,那就是把哈希槽从一个节点移到另一个节点。这意味着相同的原理能用来重新平衡集群,增/删节点,等等。
 

  • 向集群添加一个新节点,就是把一个空节点加入到集群中并把某些哈希槽从已存在的节点移到新节点上;
  • 从集群中移除一个节点,就是把该节点上的哈希槽移到其他已存在的节点上;
  • 重新平衡,就是指向一堆哈希槽在节点间迁移。


所以实现这个的核心是能把哈希槽移来移去。从实际角度看,哈希槽就只是一堆键,所以 Redis 集群在重组分片时做的就是把键从一个节点移到另一个节点。移动一个哈希槽就是移动属于这个槽的所有键。为了理解这是怎么工作的,我们需要介绍 CLUSTER 的子命令,这些命令是用来操作 Redis 集群节点上的哈希槽转换表。

有以下子命令(在这个案例中有的没用到):
 

  • 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 命令通常是用于在一个集群刚建立的时候从新给所有master节点指派哈希槽,总共有16384个。

DELSLOTS主要用于群集配置的手工修改或用于调试任务:在实践中很少使用。

如果SETSLOT 节点的形式被使用,SETSLOT用于将哈希槽指定给特定节点的ID。否则,哈希槽可以在两种特殊状态MIGRATING 和 IMPORTING。这两个特殊状态用于哈希槽从一个节点迁移到另一个。
 

  • 当一个槽被设置为MIGRATING,持有该哈希槽的节点仍会接受所有跟这个哈希槽有关的请求,但只有当查询的键还存在原节点时,原节点会处理该请求,否则这个查询会通过一个-ASK 重定向转发到迁移的目标节点。
  • 当一个槽被设置为IMPORTING,只有在接受到 ASKIN命令之后节点才会接受所有查询这个哈希槽的请求。如果客户端没有发送     ASKING命令,那么查询都会通过-MOVED 重定向错误转发到真正的哈希槽归属节点那里,这通常会发生。


这我们用实例让它哈希槽迁移更清晰些。假设我们有两个Redis master节点,称为A和B。我们想要把哈希槽8从节点A移到节点B,所以我们发送了这样的命令:
 

  • 我们向节点B发送:CLUSTER SETSLOT 8 IMPORTING A
  • 我们向节点A发送:CLUSTER SETSLOT 8 MIGRATING B


其他所有节点在每次请求的一个键是属于哈希槽8的时候,都会把客户端引向节点“A”。具体如下:
 

  • 所有关于已存在的键的查询都由节点“A”处理。
  • 所有关于不存在于节点A的键都由节点“B”处理,因为”A”将重定向客户端请求到“B”。


这种方式让我们可以不用在节点 A 中创建新的键。同时,一个叫做 redis-trib 的特殊脚本,用于重新分片和集群配置,把已存在的属于哈希槽 8的键从节点A移到节点B。这通过以下命令实现:

CLUSTER GETKEYSINSLOT slot count

上面这个命令会返回指定的哈希槽中 count 个键。对于每个返回的键,redis-trib 向节点 A 发送一个MIGRATE 命令,这样会以原子性的方式从A到B迁移指定的键(在移动键的过程中两个节点都被锁住,通常时间很短,所以不会出现竞争状况)。以下是 MIGRATE 的工作原理:

MIGRATE target_host target_port key target_database idtimeout

执行 MIGRATE 命令的节点会连接到目标节点,把序列化后的key发送过去,一旦收到OK回复就会从它自己的数据集中删除老的key。所以从一个外部客户端看来,在某个时间点,一个 key 要不就存在于节点A中要不就存在于节点B中。

在 Redis 集群中,不需要指定一个除了0号之外的数据库,但 MIGRATE 命令能用于其他跟 Redis 集群无关的的任务,所以它是一个通用的命令。MIGRATE 命令被优化了,使得即使在移动像长列表这样的复杂键仍然能做到快速。不过当在重配置一个拥有很多键且键的数据量都很大的集群的时候,如果使用它的应用程序来说就会有延时这个限制,这个过程就不是那么好了。
 

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:端口号上。 注意,即使客户端出现bug,过早地执行这个映射更新,也是没有问题的,因为它不会在查询前发送ASKING命令,节点B会用MOVED重定向错误把客户端重定向到节点A上。
 

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

虽然可以有一个Redis的群集客户端不在内存中记住哈希槽配置(哈希槽与节点的映射),并只能通过联系随机节点等待被重定向,这样的客户端将是效率非常低。

Redis的集群客户应尽量足够聪明,记忆哈希槽配置。然而这种配置不必是最新的。因为联系错误的节点只会导致一个重定向,应当触发客户视图的更新。

客户通常需要获取哈希槽与节点映射的完整列表:
 

  • 启动时保存初始的哈希槽配置.
  • 当收到 MOVED重定向.


请注意,客户可能根据MOVED重定向更新变动的哈希槽,但是这通常不是有效的,因为通常配置中多个哈希槽一起修改(例如,如果一个slave升为 master,所有归属老master的哈希槽会重新映射)。更简单对MOVED重定向做出回应是,重新获取哈希槽节点映射表。

为了获取哈希槽配置Redis的群集提供了另一种不需要的解析的命令CLUSTER NODES,并只仅提供客户端严格需要的信息。

新的命令被称为CLUSTER SLOTS并槽提供了一组哈希槽范围,关联了主从节点服务于指定的哈希槽范围。

下面是CLUSTER插槽输出的例子:
 

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



返回的数组中的每个元素的前两个子元素是该范围的始未哈希槽。附加元素表示地址端口对。第一个地址端口对是服务于哈希槽的master,和附加的地址端口对服务于相同槽slave,它不存在错误条件(即故障标志没有被设置)。
例如,输出的第一个元素表示,槽从5461至10922(开始和结束包括)由127.0.0.1:7001服务,并且可以通过127.0.0.1:7004水平扩展读负载。
CLUSTERSLOTS是不能保证返回覆盖整个16384插槽,如果群集配置不正确范围,所以客户初始化哈希槽配置时应当用NULL填充空节点,并报告一个错误,如果用户试图执行有关键的命令属于未分配的插槽。
当一个哈希槽被发现是未分配之前,返回一个错误给调用者之前,客户应该再次尝试读取哈希槽配置,检查群集现在配置是否正确。

多键操作

使用哈希标签,用户可以自由地使用多键操作。例如下面的操作是有效的:
MSET {user:1000}.name Angela {user:1000}.surname White
多键操作可能变得不可用,当键所属的哈希槽在进行重新分片。
更具体地,即使重新分片期间,多键操作目标键都存在并且处于相同节点(源或目的地节点)仍然可用。
在重新分片时,操作的键不存在或键在源节点和目的节点之间,将产生 -TRYAGAIN 错误。客户端可以一段时间后再尝试操作,或报错。
只要指定的哈希槽的迁移已经终止,所有多键操作可再次用于该散列槽。

通过slave节点水平扩展读

通常情况下从节点将客户端重定向到给定的命令哈希槽对应的master,但是客户端可以使用READONLY命令读来水平扩展读。
READONLY告诉客户端是允许读失效的数据并且不关心写请求。
当连接处于只读模式,当操作涉及到不是slave的主节点提供服务的键,集群将发送一个重定向到客户端。这可能发生的原因是:

  • 客户端发送一个命令对应的哈希槽不是由这个slave的master服务。
  • 集群重新配置(例如重新分片 )并且slave不再能够服务于给定的哈希槽命令

当发生这种情况如前面部分中说明的,客户端应该更新其哈希槽映射表。
连接的只读状态可以使用READWRITE命令清除。

<think>好的,我现在需要回答用户关于Helm部署Redis集群时生成的两个服务(redis-redis-clusterredis-redis-cluster-headless)的作用及区别,以及它们的端口配置对客户端连接的影响。首先,我需要回顾一下Kubernetes中ServiceHeadless Service的基本概念,以及它们在Redis集群部署中的具体应用。 用户之前的问题是关于Helm部署Redis主从时连接地址的配置,现在转向了Redis集群的情况,所以需要明确集群模式下的服务配置。用户提到的两个服务:redis-redis-clusterredis-redis-cluster-headless,我需要解释它们各自的作用。 首先,参考用户提供的引用,特别是引用[3]提到Headless Service没有Cluster IP,用于StatefulSet的Pod发现,而另一个Service用于外部访问负载均衡。引用[4]中使用了dig命令来解析Pod的DNS记录,这说明Headless Service为每个Pod提供了独立的DNS记录,方便直接访问。引用[1]中的例子显示服务有EXTERNAL-IP,可能是指LoadBalancer类型的服务,用于外部访问。 接下来,我需要确定在Redis集群部署中,这两个服务各自承担的角色。通常,Redis集群需要节点之间的直接通信,因为每个节点都知道其他节点的地址,所以Headless Service在这里用于提供Pod的直接DNS解析,使节点能够互相发现通信。而普通Service(如redis-redis-cluster)可能用于客户端的入口,提供负载均衡,但需要注意Redis集群客户端通常需要直接连接各个节点,而不是通过一个统一的入口,因为客户端需要知道所有节点来处理重定向分片。 但是,用户的问题中提到的是Helm部署Redis集群后生成的两个服务,我需要确认Helm chart的具体配置。例如,Bitnami的Redis集群Helm chart可能会创建两个服务:一个Headless Service用于内部Pod通信,另一个是ClusterIP或LoadBalancer类型的Service用于外部访问。需要明确这两个服务的类型、端口配置以及它们如何影响客户端连接方式。 另外,用户提到这两个服务的端口配置对客户端的影响。例如,如果普通Service暴露了6379端口,客户端可能通过该Service的Cluster IP或DNS名称来连接,但实际上在集群内部,客户端可能需要使用Headless Service来直接连接到各个Pod,特别是在需要发现所有节点的情况下,比如使用CLUSTER NODES命令或客户端自动发现。 同时,需要注意Redis集群的客户端库通常需要所有节点的地址来正确初始化连接,这时候Headless Service提供的每个Pod的DNS记录就非常要。而普通Service可能只提供一个入口点,不适合这种需要多个节点地址的场景。 还需要参考用户之前的回答,其中提到主从模式下的服务配置,现在扩展到集群模式。可能需要比较两者的异同,确保回答准确。 最后,总结两个服务的作用:Headless Service用于Pod之间的直接通信节点发现,普通Service用于客户端的统一接入(但可能不适用于所有客户端情况),并解释端口配置如何影响客户端是否需要直接连接Pod或通过Service访问。</think>以下是对 Helm 部署 Redis 集群时生成的两个服务及其影响的详细解析: --- ### 一、核心服务功能对比 | 服务名称 | 类型 | 作用场景 | 连接方式示例 | 典型端口配置 | |---------------------------------|------------------|--------------------|-----------------------------|--------------------------| | `redis-redis-cluster` | ClusterIP/LoadBalancer | **客户端统一接入** | `redis-cluster:6379` | 6379 (数据端口) | | `redis-redis-cluster-headless` | Headless Service | **节点间直接通信** | `redis-cluster-0.redis-headless:6379` | 6379 + 16379 (集群总线) | --- ### 二、服务作用深度解析 #### 1. redis-redis-cluster (普通服务) **核心功能**: - 提供虚拟 IP 负载均衡(ClusterIP 类型时)[^3] - 对外暴露统一接入点(LoadBalancer 类型时)[^1] - 通过标签选择器关联所有 Redis 节点 Pod **连接影响**: ```python # Python 客户端示例(仅适用于简单场景) import redis r = redis.Redis( host='redis-redis-cluster', # 服务名称 port=6379, password='yourpassword', decode_responses=True ) print(r.ping()) # 可能返回 False,因为集群模式需要特殊处理 ``` *注意*:这种连接方式 **不适用于原生集群模式客户端**,会导致 MOVED 重定向错误[^4] #### 2. redis-redis-cluster-headless (Headless 服务) **核心功能**: - 为每个 Pod 生成独立 DNS 记录[^2] - 暴露集群总线端口 (16379) 用于节点通信 - 支持 StatefulSet 的有序网络标识 **关键 DNS 解析格式**: ```bash # 查看具体节点的解析结果(引用[4]示例) dig +short redis-redis-cluster-0.redis-redis-cluster-headless.default.svc.cluster.local # 输出示例:10.244.1.23 ``` --- ### 三、端口配置差异对比 | 服务类型 | 6379 端口用途 | 16379 端口用途 | 客户端影响 | |------------------|----------------------------|---------------------------|--------------------------------------------------------------------------| | 普通服务 | 数据读写入口 | 无 | 需要客户端支持集群自动发现,否则会出现重定向问题 | | Headless 服务 | 节点级数据访问 | 集群总线通信 | 客户端必须使用全部节点地址初始化连接,例如:<br>`redis-cluster-{0..5}.redis-headless:6379` | --- ### 四、生产环境连接方案 #### 方案 1:智能客户端连接(推荐) ```java // Jedis 集群客户端配置示例 Set<HostAndPort> nodes = new HashSet<>(); for (int i = 0; i < 6; i++) { nodes.add(new HostAndPort( String.format("redis-redis-cluster-%d.redis-redis-cluster-headless", i), 6379 )); } JedisCluster cluster = new JedisCluster(nodes, 5000, 5000, 5, "password"); ``` #### 方案 2:DNS 自动发现 ```bash # 使用 SRV 记录自动发现节点(需预先配置) dig SRV _redis._tcp.redis-redis-cluster-headless.default.svc.cluster.local ``` --- ### 五、配置验证方法 1. **检查端点分布**: ```bash kubectl get endpoints redis-redis-cluster # 应显示所有节点 IP:6379 ``` 2. **验证 Headless 解析**: ```bash kubectl exec -it redis-cluster-0 -- nslookup redis-redis-cluster-headless # 应返回所有 Pod IP(引用[2]方法) ``` 3. **集群状态检查**: ```bash kubectl exec redis-cluster-0 -- redis-cli -a $REDIS_PASSWORD --cluster check 127.0.0.1:6379 ``` --- ### 六、故障排查指南 | 现象 | 检查点 | 解决方案 | |--------------------------|-------------------------------|------------------------------------------------------------------------| | 客户端频繁重定向 | 是否使用 Headless 服务地址 | 改用 Pod 级直接连接或启用客户端集群模式 | | 集群节点无法发现对方 | 16379 端口连通性 | 检查网络策略是否允许集群总线通信 | | 部分节点不可用 | Headless 服务的 DNS 解析完整性 | 确认 StatefulSet 副本数与服务配置匹配 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值