【Redis实现系列】集群MOVED错误与ASK错误
键操作与Moved错误
在对数据库中的16384个槽都进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了。
实现原理
当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:
如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令。
如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。
集群模式中每个节点的数据库
- 集群节点保存键值对以及键值对过期时间的方式,与单机Redis服务器保存键值对以及键值对过期时间的方式完全相同。
- 节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,而单机Redis服务器则没有这一限制。
执行步骤
计算键属于哪个槽
节点使用以下算法来计算给定键key属于哪个槽:
def slot_number(key):return CRC16(key) & 16383
其中CRC16(key)语句用于计算键key的CRC-16校验和,而&16383语句则用于计算出一个介于0至16383之间的整数作为键key的槽号。
使用CLUSTER KEYSLOT <key>命令可以查看一个给定键属于哪个槽
判断槽是否由当前节点负责处理
- 当节点计算出键所属的槽i之后,节点就会检查自己在clusterState.slots数组中的项i,判断键所在的槽是否由自己负责:
- 如果clusterState.slots[i]等于clusterState.myself,那么说明槽i由当前节点负责,节点可以执行客户端发送的命令。
- 如果clusterState.slots[i]不等于clusterState.myself,那么说明槽i并非由当前节点负责,节点会根据clusterState.slots[i]指向的clusterNode结构所记录的节点IP和端口号,向客户端返回MOVED错误,指引客户端转向至正在处理槽i的节点。
返回MOVED错误重定向
当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指引客户端转向至正在负责槽的节点。
MOVED错误的格式为:
MOVED <slot> <ip>:<port>
- 其中slot为键所在的槽,而ip和port则是负责处理槽slot的节点的IP地址和端口号
被隐藏的MOVED错误:
集群模式的redis-cli客户端在接收到MOVED错误时,并不会打印出MOVED错误,而是根据MOVED错误自动进行节点转向,并打印出转向信息,所以我们是看不见节点返回的MOVED错误的
但是,如果我们使用单机(stand alone)模式的redis-cli客户端,再次向节点7000发送相同的命令,那么MOVED错误就会被客户端打印出来,因为单机模式的redis-cli客户端不清楚MOVED错误的作用,所以它只会直接将MOVED错误直接打印出来,而不会进行自动转向。
$ redis-cli -c -p 7000 # 集群模式 127.0.0.1:7000> SET msg "happy new year!" -> Redirected to slot [6257] located at 127.0.0.1:7001 OK 127.0.0.1:7001>===========================================$ redis-cli -p 7000 # 单机模式 127.0.0.1:7000> SET msg "happy new year!" (error) MOVED 6257 127.0.0.1:7001 127.0.0.1:7000>
一个集群客户端通常会与集群中的多个节点创建套接字(Socket)连接,而所谓的节点转向实际上就是换一个套接字(Socket)来发送命令。
- 如果客户端尚未与想要转向的节点创建套接字(Socket)连接,那么客户端会先根据MOVED错误提供的IP地址和端口号来连接节点,然后再进行转向。
重分片与ASK错误
Redis集群的重新分片操作(比如新增节点)可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点。
- 重新分片操作可以在线(online)进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
实现原理
- Redis集群的重新分片操作是由Redis的集群管理软件redis-trib负责执行的,Redis提供了进行重新分片所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。
redis-trib对集群的单个槽slot进行重新分片的步骤如下:
redis-trib对目标节点发送 CLUSTER SETSLOT <slot> IMPORTING <source_id> 命令,让目标节点准备好从源节点导入(import)属于槽slot的键值对。
redis-trib对源节点发送 CLUSTER SETSLOT <slot> MIGRATING <target_id> 命令,让源节点准备好将属于槽 slot 的键值对迁移(migrate)至目标节点。
redis-trib向源节点发送 CLUSTER GETKEYSINSLOT <slot> <count> 命令,获得最多 count 个属于槽 slot 的键值对的键名(key name)。
对于步骤3获得的每个键名,redis-trib 都向源节点发送一个 MIGRATE <target_ip> <target_port> <key_name> 0 <timeout> 命令(用来标志该节点的这个 key 已经被迁移到了 target 节点),将被选中的键原子地从源节点迁移至目标节点。
重复执行步骤3和步骤4,直到源节点保存的所有属于槽 slot 的键值对都被迁移至目标节点为止。
redis-trib 向集群中的任意一个节点发送 CLUSTER SETSLOT <slot> NODE <target_id> 命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽slot已经指派给了目标节点。
ASK错误
在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。
当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:
- 源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
- 相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。
被隐藏的ASK错误
和接到MOVED错误时的情况类似,集群模式的redis-cli在接到ASK错误时也不会打印错误,而是自动根据错误提供的IP地址和端口进行转向动作。如果想看到节点发送的ASK错误的话,可以使用单机模式的redis-cli客户端:
$ redis-cli -p 7002 127.0.0.1:7002> GET "love" (error) ASK 16198 127.0.0.1:7003
ASK错误和MOVED错误的区别
ASK错误和MOVED错误都会导致客户端转向
- 原理和 dict 的 rehash 很像,都是先到路由到自己的索引表,查看是否包含要查找的 key,不包含的话就路由去新的位置。
区别
- MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点。
- 与此相反,ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:
- 在客户端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,因为可能该槽位还有没迁移完的元素,所以只有全部迁移完之后才会把这个槽位标记成 target 节点的。
- 所以这种转向不会对客户端今后发送关于槽i的命令请求产生任何影响,客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。
执行步骤
CLUSTER SETSLOT IMPORTING 实现
clusterState结构的 importing_slots_from 数组记录了当前节点正在从其他节点导入的槽:
typedef struct clusterState {// ...clusterNode *importing_slots_from[16384];// ... } clusterState;
如果importing_slots_from[i]的值不为NULL,而是指向一个clusterNode结构,那么表示当前节点正在从clusterNode所代表的节点导入槽i。
在对集群进行重新分片的时候,向目标节点发送命令:
# 将目标节点 clusterState.importing_slots_from[i] 的值设置为 source_id 所代表节点的clusterNode结构。 CLUSTER SETSLOT <i> IMPORTING <source_id>
例如:7003 客户端向节点发送以下命令```shell# 9dfb... 是节点7002 的ID 127.0.0.1:7003> CLUSTER SETSLOT 16198 IMPORTING 9dfb4c4e016e627d9769e4c9bb0d4fa208e65c26OK
CLUSTER SETSLOT MIGRATING 实现
clusterState结构的 migrating_slots_to 数组记录了当前节点正在迁移至其他节点的槽:
typedef struct clusterState {// ...clusterNode *migrating_slots_to[16384];// ... } clusterState;
如果migrating_slots_to[i]的值不为NULL,而是指向一个clusterNode结构,那么表示当前节点正在将槽i迁移至clusterNode所代表的节点。
在对集群进行重新分片的时候,向源节点发送命令:
# 将源节点clusterState.migrating_slots_to[i]的值设置为target_id所代表节点的clusterNode结构。 CLUSTER SETSLOT <i> MIGRATING <target_id>
例如:7002客户端向节点发送以下命令
# 0457... 是节点7003 的ID 127.0.0.1:7002> CLUSTER SETSLOT 16198 MIGRATING 04579925484ce537d3410d7ce97bd2e260c459a2 OK
ASK 错误流程
如果节点收到一个关于键key的命令请求,并且键key所属的槽i正好就指派给了这个节点,那么节点会尝试在自己的数据库里查找键key,如果找到了的话,节点就直接执行客户端发送的命令。
与此相反,如果节点没有在自己的数据库里找到键key,那么节点会检查自己的clusterState.migrating_slots_to[i],看键key所属的槽i是否正在进行迁移,如果槽i的确在进行迁移的话,那么节点会向客户端发送一个ASK错误,引导客户端到正在导入槽i的节点去查找键key。
接到ASK错误的客户端会根据错误提供的IP地址和端口号,转向至正在导入槽的目标节点,然后首先向目标节点发送一个ASKING命令,之后再重新发送原本想要执行的命令。
ASKING 命令
ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识,以下是该命令的伪代码实现:
def ASKING():# 打开标识client.flags |= REDIS_ASKING# 向客户端返回OK 回复reply("OK")
- 在一般情况下,如果客户端向节点发送一个关于槽i的命令,而槽i又没有指派给这个节点的话,那么节点将向客户端返回一个MOVED错误;但是,如果节点的clusterState.importing_slots_from[i]显示节点正在导入槽i,并且发送命令的客户端带有REDIS_ASKING标识,那么节点将破例执行这个关于槽i的命令一次
- 另外要注意的是,客户端的REDIS_ASKING标识是一个一次性标识,当节点执行了一个带有REDIS_ASKING标识的客户端发送的命令之后,客户端的REDIS_ASKING标识就会被移除。
127.0.0.1:7003> GET "love" # 单机模式,love 所在的槽位正在被重分配 (error) MOVED 16198 127.0.0.1:7002#========================================================127.0.0.1:7003> ASKING # 打开 REDIS_ASKING 标识 OK 127.0.0.1:7003> GET "love" # 访问一次后自动移除 REDIS_ASKING 标识 "you get the key 'love'" 127.0.0.1:7003> GET "love" # 此时 REDIS_ASKING 标识未打开,执行失败 (error) MOVED 16198 127.0.0.1:7002
【Redis实现系列】集群MOVED错误与ASK错误相关推荐
- Redis主从复制和集群配置系列之四
非常感谢 http://blog.csdn.net/dc_726/article/details/48552531 Redis技术学习 https://www.itkc8.com 全面剖析Redis ...
- Redis系列---集群模式
目录 1. 数据分片 1.1. 哈希算法 1.1.1. 优点 1.1.2. 缺点 1.2. 一致性哈希算法 1.2.1. 优点 1.2.2. 缺点 1.3. 范围算法 1.3.1. 优点 1.3.2. ...
- 2W 字详解 Redis 6.0 集群环境搭建实践
原文链接:https://www.cnblogs.com/hueyxu/p/13884800.html 本文是Redis集群学习的实践总结(基于Redis 6.0+),详细介绍逐步搭建Redis集群环 ...
- Redis高可用集群-哨兵模式(Redis-Sentinel)搭建配置教程【Windows环境】
================================================= 人工智能教程.零基础!通俗易懂!风趣幽默!大家可以看看是否对自己有帮助! 点击查看高清无码教程 == ...
- redis 主从 哨兵 集群 及原理
1.主从哨兵 1.主从哨兵架构图: 此图为最常见的一主两从结构,一个master主机,两个slave主机.每台主机上都运行着两个进程: redis-server 服务,处理redis正常的数据操作与响 ...
- redis 04:Redis高可用集群
文章目录 一.Redis集群方案比较 二.Redis高可用集群搭建 三.Java操作redis集群 四.Redis集群原理分析 五.集群伸缩 5.1 集群扩展 5.1 缩容集群 六.总结 以下参考了图 ...
- CentOS7下安装Redis伪集群(基于Redis官方Cluster集群模式版本redis-5.0.10)
文章目录 Redis简介 什么是redis redis的优点 Redis集群都有哪些模式 主从复制(Master-Slave Replication) 哨兵模式(Sentinel) Redis官方 C ...
- redis 持久化 + 主从复制+ 集群
2019独角兽企业重金招聘Python工程师标准>>> 一. Linux 下的 Redis 安装 && 启动 && 关闭 && 卸载 ...
- Redis 3种集群方式,别傻傻分不清!
文章目录 Redis 3种集群方式,别傻傻分不清! 1 redis 主从模式配置 前言 redis 主从模式配置 首先更改配置文件 (redis.windows.conf ) 配置主从配置 (redi ...
- Redis 多服务器集群搭建
Redis 多服务器集群搭建 近期,想到之前使用的Redis集群测试使用的是单服务器上的伪集群,重温<Redis深度历险-核心原理与应用实践>的案例,觉得还是搭建一下多服务器集群来玩一玩会 ...
最新文章
- oracle中快速复制数据表(创建数据表)
- 光流 | 基于KLT(Kanade-Lucas-Tomasi)特征点跟踪算法(附代码,可扩展)
- weex web获取html高度,weex 中的 web 标签如何控制高度?
- map集合的putall_Map.put和Map.putAll方法之间的区别?
- CentOS6.9安装Kafka
- php 调用日历控制,基于ThinkPHP实现的日历功能实例详解
- ffplay.exe操作方式
- JVM GC一篇通 - 基础与调优
- 租房系统代码java_基于Java的租房管理系统的设计及实现.doc
- win10开始屏幕计算机,为什么Win10系统开始屏幕没反应?解决Win10系统开始屏幕没反应的方法...
- notepad++下的字体设置
- VS2010 编译 openssl 源代码(输出 libeay32 and ssleay32 静态库和动态库)
- 三维地理信息系统空间的可视化分析
- Linux 常用软件
- Thinkpad E430c使用u盘安装系统
- java调用阿里云短信服务器-发送短信
- 机器学习系列(五) -- 逻辑回归(莺尾花数据集)
- 面试总结+感悟+分享
- 哪款蓝牙耳机音质好又耐用?便宜耐用的蓝牙耳机
- 【前沿技术】浅析搜狗AI主播背后的核心技术