xyZGHio

本是青灯不归客,却因浊酒恋风尘

0%

浅谈Redis Cluster 集群的MOVED、ASK重定向

本文介绍Redis Cluster集群的MOVED、ASK重定向

abstract.png

搭建集群

利用Docker Compose搭建一个3主3从的Redis Cluster集群,docker-compose.yml文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 构建一个3主3从的Redis集群
# Compose 版本
version: '3.8'

# 定义服务
services:

Redis-Service-1:
image: redis:7.0
container_name: node-1
command: [ "redis-server", "/etc/redis/redis.conf" ]
ports:
- "6371:6379"
- "16371:16379"
volumes:
- /Users/zgh/Docker/RedisCluster_3M3S/RedisConf/redis.conf:/etc/redis/redis.conf
networks:
# 使用名为redis_cluster_3m3s_net的网络并设置容器IP
redis_cluster_3m3s_net:
ipv4_address: 120.120.120.11

Redis-Service-2:
image: redis:7.0
container_name: node-2
command: [ "redis-server", "/etc/redis/redis.conf" ]
ports:
- "6372:6379"
- "16372:16379"
volumes:
- /Users/zgh/Docker/RedisCluster_3M3S/RedisConf/redis.conf:/etc/redis/redis.conf
networks:
# 使用名为redis_cluster_3m3s_net的网络并设置容器IP
redis_cluster_3m3s_net:
ipv4_address: 120.120.120.12

Redis-Service-3:
image: redis:7.0
container_name: node-3
command: [ "redis-server", "/etc/redis/redis.conf" ]
ports:
- "6373:6379"
- "16373:16379"
volumes:
- /Users/zgh/Docker/RedisCluster_3M3S/RedisConf/redis.conf:/etc/redis/redis.conf
networks:
# 使用名为redis_cluster_3m3s_net的网络并设置容器IP
redis_cluster_3m3s_net:
ipv4_address: 120.120.120.13

Redis-Service-4:
image: redis:7.0
container_name: node-4
command: [ "redis-server", "/etc/redis/redis.conf" ]
ports:
- "6374:6379"
- "16374:16379"
volumes:
- /Users/zgh/Docker/RedisCluster_3M3S/RedisConf/redis.conf:/etc/redis/redis.conf
networks:
# 使用名为redis_cluster_3m3s_net的网络并设置容器IP
redis_cluster_3m3s_net:
ipv4_address: 120.120.120.14

Redis-Service-5:
image: redis:7.0
container_name: node-5
command: [ "redis-server", "/etc/redis/redis.conf" ]
ports:
- "6375:6379"
- "16375:16379"
volumes:
- /Users/zgh/Docker/RedisCluster_3M3S/RedisConf/redis.conf:/etc/redis/redis.conf
networks:
# 使用名为redis_cluster_3m3s_net的网络并设置容器IP
redis_cluster_3m3s_net:
ipv4_address: 120.120.120.15

Redis-Service-6:
image: redis:7.0
container_name: node-6
command: [ "redis-server", "/etc/redis/redis.conf" ]
ports:
- "6376:6379"
- "16376:16379"
volumes:
- /Users/zgh/Docker/RedisCluster_3M3S/RedisConf/redis.conf:/etc/redis/redis.conf
networks:
# 使用名为redis_cluster_3m3s_net的网络并设置容器IP
redis_cluster_3m3s_net:
ipv4_address: 120.120.120.16

# 定义网络
networks:
# 定义一个名为redis_cluster_3m3s_net的网络
redis_cluster_3m3s_net:
ipam:
config:
# 设置网段
- subnet: 120.120.120.0/24

Redis指定版本的配置文件redis.conf可通过下述链接下载对应版本的安装包后解压后获取

1
http://download.redis.io/releases/

修改Redis配置文件redis.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...

# 注释bind配置项
# bind 127.0.0.1 -::1

# 设为 no, 关闭保护模式
protected-mode no

# 端口设为6379
port 6379

# 设为no, 因为docker启动时会通过-d参数来让其实现后台运行
daemonize no

# 修改数据库数量, 用于验证配置文件是否生效
databases 5

# 设置访问主库时的密码
masterauth "52996"

# 设置Redis密码
requirepass 52996

# 使能集群模式
cluster-enabled yes

# 集群是否要求槽全覆盖
# yes:当负责某个槽的主库下线且没有相应的从库进行故障恢复时,集群整体不可用。即其他槽的节点在线也无法访问
# no:当负责某个槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用。即可访问其他槽的数据
cluster-require-full-coverage yes

...

这里对于集群中的6个节点,均使用上述同一个配置文件。执行下述命令组建集群

1
2
3
4
5
6
docker exec -it node-1 \
redis-cli -p 6379 -a 52996 --cluster create \
120.120.120.11:6379 120.120.120.12:6379 \
120.120.120.13:6379 120.120.120.14:6379 \
120.120.120.15:6379 120.120.120.16:6379 \
--cluster-replicas 1

命令执行过程中,当分配好每个节点的槽范围后,需要输入yes进行确认,以便完成集群构建。其中,这里node 1、node 2、node 3节点为Master节点

MOVED 重定向

Redis Custer中,客户端可以向集群中任意节点发送请求。此时当前节点先对Key进行CRC 16计算,然后按16384取模确定Slot槽。确定该Slot槽所对应的节点,如果该Slot是当前节点负责,且该Key存在于该Slot中,则直接返回该Key对应的结果;如果该Slot不是当前节点负责,则返回MOVED重定向告知客户端对应的节点地址信息

figure 1.jpg

我们在node 1节点上通过 cluster keyslot [key] 命令确定名为name的key所对应的Slot槽编号为5798。而通过cluster nodes命令结果,我们知道node 2节点的槽范围为[5461-10922]。结合二者可知,名为name的key实际位于node 2节点。故我们此时继续在node 1节点执行get name命令会返回MOVED重定向,告知我们node 2节点的地址信息

显然,当我们直接在node 2节点上查询key为name的数据,此时是可以直接获取结果的

事实上,对于redis-cli工具而言,当我们添加 -c选项 后,即以集群方式执行redis-cli命令后,其会帮我们自动处理MOVED重定向

figure 2.jpg

ASK 重定向

概述

Ask重定向发生于Redis集群进行伸缩(扩容/缩容)时,由于此时会进行Slot槽迁移。当我们去源节点访问时,数据可能已经迁移到目标节点中。故此时需要借助Ask重定向来解决该问题。具体地,在将A节点中的某个槽迁移到B节点过程中:

  • 当A节点该Slot槽设置为 MIGRATING迁出状态 后,A节点依然可以接受有关此Slot槽的查询命令。如果该Key依然存在于该Slot槽中,则直接返回结果;如果该Key不存在于该Slot槽,说明该Key可能已经迁移到目的节点B当中了,故其会返回ASK重定向以告知客户端该Slot槽迁入的目的节点B地址信息

  • 当B节点该Slot槽设置为 IMPORTING迁入状态 时,B节点可以接受有关此哈希槽的查询命令。但前提是客户端向B节点发送该Key的查询命令之前,必须要先发送ASKING命令。否则,B节点会返回MOVED重定向以告知客户端A节点的地址信息

figure 3.jpg

手动迁移槽的流程

在手动进行迁移槽之前,我们先介绍下手动迁移槽的流程、命令

1.目标节点上执行cluster setslot命令的importing子命令。将目标节点的该槽设置为 IMPORTING迁入状态

1
2
# 该命令必须在目标节点上执行 !!
cluster setslot <Slot槽> importing <source节点ID>

2.源节点上执行cluster setslot命令的migrating子命令。将源节点的该槽设置为 MIGRATING迁出状态

1
2
# 该命令必须在源节点上执行 !!
cluster setslot <Slot槽> migrating <destination节点ID>

3.源节点上执行cluster getkeysinslot命令获取槽中最多count个的key

1
2
# 该命令必须在源节点上执行 !!
cluster getkeysinslot <Slot槽> <count>

4.源节点上执行migrate命令。将key从源节点迁移到目标节点中

1
2
# 该命令必须在源节点上执行 !!
migrate <destination节点IP> <destination节点Port> <key | ""> destination-db timeout [COPY] [REPLACE] [AUTH password | AUTH2 username password] [KEYS key [key ...]]

该命令部分参数说明如下:

  • key|”” :旧版本Redis下migrate命令只支持迁移一个键,所以此处即为迁移的键名;但在Redis 3.0.6版本开始支持迁移多个键,如果当前需要迁移多个键,此处为空字符串 “”
  • destination-db :目标节点的Redis数据库索引。例如要迁移到0号数据库,此处使用 0
  • timeout :迁移的超时时间,Unit:ms
  • COPY :添加此选项,则迁移后不会删除源节点中的Key
  • REPLACE :添加此选项,则会替换目的节点中已存在的Key
  • AUTH :连接目标节点的密码
  • AUTH2 :连接目标节点的用户名、密码(Redis 6版本的ACL认证)
  • KEYS key [key …] :迁移多个键。例如迁移key1、key2、key3时,此处写法为 KEYS key1 key2 key3

5. 重复执行上述 步骤3、步骤4。直到将源节点中该槽下所有的键全部迁移到目标节点

6. 在集群中目的节点、源节点、其他主节点上依次执行cluster setslot命令的node子命令。用于通知该槽现已分配给目标节点。具体地执行次序为 :

6.1 在目的节点上执行cluster setslot命令的node子命令
6.2 在源节点上执行cluster setslot命令的node子命令
6.3 在其他主节点上执行cluster setslot命令的node子命令

1
2
# 该命令在集群中所有主节点(目的节点、源节点、其他主节点)上全部执行依次
cluster setslot <Slot槽> node <目标节点ID>

Note

  • 步骤1、步骤2的先后顺序非常重要,不可以调换
  • 步骤6.1、步骤6.2的先后顺序非常重要,不可以调换
  • 事实上,步骤6.3在技术上并不是必需的。即是可选的,因为相关配置最终会在集群中自行传播。但执行该步骤可以减少不必要的重定向
  • 虽然执行命令的node子命令后,可以清除槽的MIGRATING迁出状态IMPORTING迁入状态;但在某些意外场景下,当集群陷入错误状态后,可通过 cluster setslot [slot槽] stable 命令来清除槽的MIGRATING迁出状态IMPORTING迁入状态

实践

现在,我们通过手动迁移槽来进行演示。我们向集群中添加了3个的Key。这3个key的Slot槽均为7000,位于node 2节点当中

1
2
3
set 47344|273766|70329104160040|key_39015 "Aaron"
set 47344|273766|70329104160040|key_89793 "Bob"
set 47344|273766|70329104160040|key_92937 "Celia"

figure 4.jpg

现在我们期望将node 2节点中的Slot槽7000迁移到node 3节点中

首先,将node 3、node 2节点中Slot槽7000分别设置为IMPORTING迁入状态MIGRATING迁出状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查看集群中节点ID
docker exec -it node-5 redis-cli -p 6379 -a 52996 cluster nodes

# 进入目标节点node 3
docker exec -it node-3 redis-cli -p 6379 -a 52996

# 进入目标节点node 3后执行该命令
# 将node 3节点中Slot槽7000 设置为 IMPORTING迁入状态
cluster setslot 7000 importing <node 2 节点ID>

# 必须在node 3节点上执行该命令,以确认 Slot槽7000的迁入状态是否被正确设置
docker exec -it node-3 redis-cli -p 6379 -a 52996 cluster nodes | grep "120.120.120.13"


# 进入源节点node 2
docker exec -it node-2 redis-cli -p 6379 -a 52996

# 进入源节点node 2后执行该命令
# 将node 2节点中Slot槽7000 设置为 MIGRATING迁出状态
cluster setslot 7000 migrating <node 3 节点ID>

# 必须在node 2节点上执行该命令,以确认 Slot槽7000的迁出状态是否被正确设置
docker exec -it node-2 redis-cli -p 6379 -a 52996 cluster nodes | grep "120.120.120.12"

figure 5.jpg

然后,获取源节点node 2中Slot槽7000中的Key,并将其中两个Key迁移到node 3节点中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 进入源节点node 2
docker exec -it node-2 redis-cli -p 6379 -a 52996

# 进入源节点node 2后执行该命令
# 获取源节点node 2下Slot槽7000中最多10个的Key
cluster getkeysinslot 7000 10

# 进入源节点node 2后执行该命令
# 将 47344|273766|70329104160040|key_39015、47344|273766|70329104160040|key_89793
# 这两个Key迁移到 node 3 节点中
migrate 120.120.120.13 6379 "" 0 5000 AUTH 52996 KEYS 47344|273766|70329104160040|key_39015 47344|273766|70329104160040|key_89793

# 查看各Master下Key数量等统计信息
docker exec -it node-1 redis-cli -c -p 6379 -a 52996 --cluster info 120.120.120.14:6379

figure 6.jpg

至此,我们分别在node 2、node 3中尝试访问这三个Key,注意redis-cli不要使用-c选项,以便观察到重定向的具体信息。下图结果符合预期,具体地:

  • 当我们访问源节点node 2时,对于node 2节点7000槽中存在的Key而言(例如,该Key未迁出),其会直接返回结果;对于node 2节点7000槽中不存在的Key(例如,该Key可能已经迁出)来说,其会返回ASK重定向以告知客户端该Slot槽7000迁入的目的节点地址信息
  • 当我们访问目的节点node 3时,如果每次访问属于7000槽的Key时,没有先发送ASKING命令。不论该Key是否已经迁入node 3节点中,其均会返回MOVED重定向以告知客户端该槽7000的源节点地址信息
  • 当我们访问目的节点node 3时,如果每次访问属于7000槽的Key时,先发送ASKING命令。不论该Key是否已经迁入node 3节点中,则其都只会在node 3节点中进行查询。具体地,如果该Key迁入的话,则直接返回结果;如果该Key未迁入,则直接返回null

figure 7.jpg

现在,我们将node 2节点7000槽中的最后一个Key也迁移到node 3节点当中,并最终完成整个迁移过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 进入源节点node 2
docker exec -it node-2 redis-cli -p 6379 -a 52996

# 进入源节点node 2后执行该命令
# 获取源节点node 2下Slot槽7000中最多10个的Key
cluster getkeysinslot 7000 10

# 进入源节点node 2后执行该命令
# 将 47344|273766|70329104160040|key_92937 迁移到 node 3 节点中
migrate 120.120.120.13 6379 "" 0 5000 AUTH 52996 KEYS "47344|273766|70329104160040|key_92937"

# 在目的节点node 3上执行cluster setslot命令的node子命令
docker exec -it node-3 redis-cli -p 6379 -a 52996 cluster setslot 7000 node <node 3 节点ID>

# 在源节点node 2上执行cluster setslot命令的node子命令
docker exec -it node-2 redis-cli -p 6379 -a 52996 cluster setslot 7000 node <node 3 节点ID>

# 在其他主节点node 1上执行cluster setslot命令的node子命令
docker exec -it node-1 redis-cli -p 6379 -a 52996 cluster setslot 7000 node <node 3 节点ID>

figure 8.jpg

当迁移完成后,不仅node 3、node 2节点中Slot槽7000的IMPORTING迁入状态MIGRATING迁出状态被清除了;同时,
现在对Slot槽7000中Key的访问,也会MOVED重定向到node 3节点了

figure 9.jpg

总结

从Redis客户端角度出发,处理MOVED、ASK重定向的思路是不一样。具体地:

  • 对于MOVED重定向而言,Redis客户端需要更新客户端本地的集群拓扑结构,避免发生没有必要的重定向
  • 对于ASK重定向而言,Redis客户端不应该更新客户端本地的集群拓扑结构,因为下次查询的某个Key,可能还未迁移到目的节点中,依然存在于源节点当中
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝