本文介绍Redis Cluster集群的MOVED、ASK重定向
搭建集群
利用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
|
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: 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: 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: 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: 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: 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: ipv4_address: 120.120.120.16
networks: 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
| ...
protected-mode no
port 6379
daemonize no
databases 5
masterauth "52996"
requirepass 52996
cluster-enabled yes
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重定向告知客户端对应的节点地址信息
我们在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重定向
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节点的地址信息
手动迁移槽的流程
在手动进行迁移槽之前,我们先介绍下手动迁移槽的流程、命令
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"
|
现在我们期望将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
| docker exec -it node-5 redis-cli -p 6379 -a 52996 cluster nodes
docker exec -it node-3 redis-cli -p 6379 -a 52996
cluster setslot 7000 importing <node 2 节点ID>
docker exec -it node-3 redis-cli -p 6379 -a 52996 cluster nodes | grep "120.120.120.13"
docker exec -it node-2 redis-cli -p 6379 -a 52996
cluster setslot 7000 migrating <node 3 节点ID>
docker exec -it node-2 redis-cli -p 6379 -a 52996 cluster nodes | grep "120.120.120.12"
|
然后,获取源节点node 2中Slot槽7000中的Key,并将其中两个Key迁移到node 3节点中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| docker exec -it node-2 redis-cli -p 6379 -a 52996
cluster getkeysinslot 7000 10
migrate 120.120.120.13 6379 "" 0 5000 AUTH 52996 KEYS 47344|273766|70329104160040|key_39015 47344|273766|70329104160040|key_89793
docker exec -it node-1 redis-cli -c -p 6379 -a 52996 --cluster info 120.120.120.14:6379
|
至此,我们分别在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
现在,我们将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
| docker exec -it node-2 redis-cli -p 6379 -a 52996
cluster getkeysinslot 7000 10
migrate 120.120.120.13 6379 "" 0 5000 AUTH 52996 KEYS "47344|273766|70329104160040|key_92937"
docker exec -it node-3 redis-cli -p 6379 -a 52996 cluster setslot 7000 node <node 3 节点ID>
docker exec -it node-2 redis-cli -p 6379 -a 52996 cluster setslot 7000 node <node 3 节点ID>
docker exec -it node-1 redis-cli -p 6379 -a 52996 cluster setslot 7000 node <node 3 节点ID>
|
当迁移完成后,不仅node 3、node 2节点中Slot槽7000的IMPORTING迁入状态、MIGRATING迁出状态被清除了;同时,
现在对Slot槽7000中Key的访问,也会MOVED重定向到node 3节点了
总结
从Redis客户端角度出发,处理MOVED、ASK重定向的思路是不一样。具体地:
- 对于MOVED重定向而言,Redis客户端需要更新客户端本地的集群拓扑结构,避免发生没有必要的重定向
- 对于ASK重定向而言,Redis客户端不应该更新客户端本地的集群拓扑结构,因为下次查询的某个Key,可能还未迁移到目的节点中,依然存在于源节点当中