美文网首页redis系列
redis系列(八):槽

redis系列(八):槽

作者: 范柏柏 | 来源:发表于2020-06-01 20:13 被阅读0次

槽是什么

redis集群的整个数据库被分为16384(2的14次方)个槽(slot),数据库中的每个key都属于这16384个槽的其中一个,一个槽可以管理多个key,一个key值属于一个槽。

集群中每个节点都可以处理0-16384个槽,如果集群中只有一个节点,那么这个节点就是自己处理所有的槽。

当16384个槽都有节点在处理时,集群处于上线状态(ok),相反,有任意一个槽没有得到处理,就处于下线状态(fail)。

在7000节点上执行:

cluster addslots <slot> [slot ...]
cluster addslots 0 1 2 3 4 ... 5000

那么槽0-槽5000指派给节点7000负责。

再手动把5001-10000指派给7001,10001-16383指派给7002。现在槽都指派了,集群处于上线状态。

记录槽信息

很显然,记录一个节点负责哪些槽,肯定是节点自己维护的。那肯定就在clusterNode
里了。

struct clusterNode {
    
    // ... 上一章节记录的属性
    
    // 二进制位数组,数组长度为16384/8=2048字节,刚好包括16384个二进制位。
    unsigned char slots[16384/8];
    
    // 记录该节点处理槽的数量,也就是slots数组中值为1的二进制位的数量
    int numslots;
    
    // ...
}

redis以0为起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,根据索引i上的二进制位,判断节点是否负责处理槽i。

  • 在索引i上的值为1 --> 表示节点负责处理槽i
  • 在索引i上的值为0 --> 表示节点不负责处理槽i
槽数据结构.png

只有在slots[0]中的0-7位是1,表示这个节点值负责槽0槽7。

看一下时间复杂度。
取出和设置slots数组中的任意一个二进制位的复杂度都是O(1),所以

  • 检查该节点是否负责处理某个槽
  • 将某个槽支配给该节点

这两个操作的时间复杂度都是O(1)

那,查询某一个槽是哪个节点负责的呢。以当前的结构需要遍历一遍集群中的redis,复杂度为O(n),所以还会有一个数据结构。

很显然,记录槽在哪个节点上,肯定是集群维护的,那肯定就在clusterState里了

typedef struct clusterState {
    
    // ... 上一章节记录的属性
    
    // key 为槽索引,槽1就是slots[1]
    // value 为负责该槽的节点 clusterNode
    // 如果没有节点负责,那么指向 null
    clusterNode *slots[16384];
    
    // ...
}

有这个属性了,查询某一个槽是哪个节点负责的,时间复杂度也就O(1)了

现在合起来就有一张集群的大图了

槽完整大图.png

计算key属于哪个槽

def slot_number(key):
    return CRC16(key) % 16383

先计算key的CRC-16校验和, % 16383位运算计算出16383之间的整数,以这个数作为key的槽号。

使用

cluster keyslot <key> 命令 可以查看一个给定键属于哪个槽

当计算出key所在的槽号后,节点就会检查这个槽号归不归自己管,在clusterState.slots中O(1)判断。

  • clusterState.slots[i] 等于 clusterState.shelf,那么归自己管,直接执行客户端发送的命令
  • 如果不等于。该节点会返回clusterState.slots[i]指向的节点信息,向客户端返回MOVED错误,指引客户端转向至正确的节点。

举个例子:当在7000这台机器执行命令的时候,7000发现这个key在7001上。那么会发生如下动作。

  1. 在7000服务器上,客服端用户执行:
set phone 123123123
  1. 7000计算phone的槽为10086,并判断归7001负责,返回给客户端:
MOVED 10086 127.0.0.1:7001
  1. 客户端收到后,会自动把服务区转换到7001上,客户端自动继续执行,2-3步对用户无感知:
set phone 123123123

成功。

为什么不用一致性哈希呢

原理不太解释了,就一句话吧,一致性哈希对于数据分布、节点位置的控制不是很友好,这种轻便的哈希槽更符合redis这种场景。

重新分片

redis集群的重新分片操作是由redis的集群管理软件redis-trib执行负责的。步骤如下:

  1. redis-trib对目标节点发送cluster setslot <slot> migrating <target_id> 命令。让目标节点准备好从源节点导入(import)属于槽slot的键值对

  2. redis-trib对源节点发送 cluster setslot <slot> migrating <target_id> 命令,让源节点准备好将属于槽slot的键值对迁移(migrate)至目标节点

  3. redis-trib向源节点发送cluster getkeysinslot <slot> <count>命令,获取最多count个属于槽slot的键值对的键名(key name)

  4. 对步骤3获得的每个键名,redis-trib都向源节点发送一个migrate <target_ip> <target_port> <key_name> 0 <timeout>命令,将被选中的键原子地从源节点迁移至目标节点

  5. 重复执行3-4,直到源节点保存的所有属于槽slot的键值对都被迁移到目标节点为止。

  6. redis-trib向集群中的任意一个节点发送 cluster setslot <slot> node <target_id>命令,将槽slot指派给目标节点,这一消息会发送至整个集群,让集群所有节点都直到该slot被迁移到目标节点了。

迁移期间访问问题

当在迁移过程中,如果被访问的slot,可能会有部分key存在在源节点,有部分在目标节点中。

  1. 当客户端发送请求到源节点的时候,源节点会查看对应的key是否还在本节点,如果存在,则直接执行命令返回给客户。如果不存在,则会给客户端返回一个ASK错误,指引客户端往正在导入的目标slot去请求对应的key。客户端可以通过返回的ASK错误中的目标节点进行对应KEY的请求。
    2) 当客户端发送请求到目标节点时。
  • 如果客户端请求时,带上ASKING标识,由目标节点会执行对应KEY的查询。正常情况下,如果是通过查询源slot,获取ASK错误之后,再到目标节点进行查询的时候,需要带上ASKING标识。
  • 如果客户端请求时,未带上ASKING标识,原由上,对应的slot还属于源节点,则目标节点会拒绝执行KEY查询,会返回一个MOVED错误给客户端,告诉客户端对应的KEY的slot属于源节点。正常情况下,如果第一次请求KEY到了正在迁移的目标节点,则会收到MOVED错误。

这里有个小知识点,ASK错误,和MOVED错误相似,是在重新分片的过程中发生key的转移导致的未命中重定向。ASK只是两个节点在槽迁移过程中的临时措施。

知识点:每个redis节点,存多少槽,存那些槽。是手动设置的。

相关文章

网友评论

    本文标题:redis系列(八):槽

    本文链接:https://www.haomeiwen.com/subject/oadhzhtx.html