深入浅析Redis 集群伸缩原理


Posted in Redis onMay 15, 2021

Redis 节点分别维护自己负责的槽和对应的数据。伸缩原理:Redis 槽和对应数据在不同节点之间移动

一、集群扩容

1. 手动扩容

(1) 准备节点 9007,并加入集群

192.168.11.40:9001> cluster meet 192.168.11.40 9007

【注意】若 cluster meet 加入已存在于其它集群的节点,会导致集群合并,造成数据错乱!。建议使用 redis-cli 的 add-node:

# 若节点已加入其它集群或包含数据,会报错
add-node    new_host:new_port existing_host:existing_port
            --cluster-slave  # 直接添加为从节点
            --cluster-master-id <arg>  # 从节点对应的主节点id

(2) 迁移槽和数据

槽在迁移过程中集群可以正常提供读写服务

首先确定原有节点的哪些槽需要迁移到新节点。确保每个节点负责相似数量的槽,保证各节点的数据均匀

槽是 Redis 集群管理数据的基本单位。数据迁移是逐槽进行的

槽迁移流程:

深入浅析Redis 集群伸缩原理

  • 目标节点准备导入槽的数据:目标节点执行cluster setslot {slot} importing {sourceNodeId}
  • 源节点准备迁出槽的数据:源节点执行cluster setslot {slot} migrating {targetNodeId}
  • 获取 count 个属于槽 slot 的键:源节点执行cluster getkeysinslot {slot} {count}
  • 迁移键:源节点执行migrate {targetIp} {targetPort} "" 0 {timeout} keys {keys...},把键通过流水线(pipeline)机制批量迁移到目标节点。Redis3.0.6 后才支持批量迁移
  • 重复上两步,直到槽下所有的键值数据迁移到目标节点
  • 向集群所有主节点通知槽被分配给目标节点:集群内所有主节点执行cluster setslot {slot} node {targetNodeId}

内部伪代码:

def move_slot(source,target,slot):
    # 目标节点准备导入槽
    target.cluster("setslot",slot,"importing",source.nodeId);
    # 目标节点准备全出槽
    source.cluster("setslot",slot,"migrating",target.nodeId);
    while true :
        # 批量从源节点获取键
        keys = source.cluster("getkeysinslot",slot,pipeline_size);
        if keys.length == 0:
            # 键列表为空时,退出循环
            break;
        # 批量迁移键到目标节点
        source.call("migrate",target.host,target.port,"",0,timeout,"keys",keys);
        # 向集群所有主节点通知槽被分配给目标节点
        for node in nodes:
            if node.flag == "slave":
                continue;
            node.cluster("setslot",slot,"node",target.nodeId);

(3) 将 9001 的槽 4096 迁移到 9007 中

准备数据

192.168.11.40:9001> set key:test:5028 value:5028
192.168.11.40:9001> set key:test:68253 value:68253

目标节点准备工作

192.168.11.40:9007> cluster nodes
8ccdb0963411ebd05ce21952bdd4b7597825afdc 192.168.11.40:9001@19001 master - 0 1620928869000 2 connected 0-5461
bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d 192.168.11.40:9007@19007 myself,master - 0 1620928868000 0 connected
...
# 9007 准备导入槽 4096 的数据
192.168.11.40:9007> cluster setslot 4096 importing 8ccdb0963411ebd05ce21952bdd4b7597825afdc
OK
# 槽 4096 已开启导入状态
192.168.11.40:9007> cluster nodes
bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d 192.168.11.40:9007@19007 myself,master - 0 1620928959000 0 connected [4096-<-8ccdb0963411ebd05ce21952bdd4b7597825afdc]
...

源节点准备工作

# 9001 准备导出槽 4096 数据
192.168.11.40:9001> cluster setslot 4096 migrating bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d
OK
# 槽 4096 已开启导出状态
192.168.11.40:9001> cluster nodes
8ccdb0963411ebd05ce21952bdd4b7597825afdc 192.168.11.40:9001@19001 myself,master - 0 1620929179000 2 connected 0-5461 [4096->-bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d]
...

导出数据

# 获取 100 个属于槽 4096 的键
192.168.11.40:9001> cluster getkeysinslot 4096 100
1) "key:test:5028"
2) "key:test:68253"
# 查看数据
192.168.11.40:9001> mget key:test:5028 key:test:68253
1) "value:5028"
2) "value:68253"
# 迁移这2个键:migrate 命令保证了每个键迁移过程的原子性
192.168.11.40:9001> migrate 192.168.11.40 9007 "" 0 5000 keys key:test:5028 key:test:68253
OK
# 再次查询会报 ASK 错误:引导客户端找到数据所在的节点
192.168.11.40:9001> mget key:test:5028 key:test:68253
(error) ASK 4096 192.168.11.40:9007

通知所有主节点:槽 4096 指派给 9007

192.168.11.40:9001> cluster setslot 4096 node bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d
192.168.11.40:9002> cluster setslot 4096 node bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d
192.168.11.40:9003> cluster setslot 4096 node bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d
192.168.11.40:9007> cluster setslot 4096 node bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d

查看最终结果

192.168.11.40:9007> cluster nodes
8ccdb0963411ebd05ce21952bdd4b7597825afdc 192.168.11.40:9001@19001 master - 0 1620931743303 7 connected 0-4095 4097-5461
bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d 192.168.11.40:9007@19007 myself,master - 0 1620931741000 8 connected 4096
...

2. 使用 redis-cli 扩容

redis-cli 提供了槽重分片功能

reshard 命令参数详解:

reshard    host:port  # 集群内任意节点地址
           --cluster-from <arg>  # 源节点id,逗号分隔
           --cluster-to <arg>  # 目标节点id,只有一个
           --cluster-slots <arg>  # 迁移多少个槽
           --cluster-yes  # 确认执行reshard
           --cluster-timeout <arg>  # 每次 migrate 操作的超时时间,默认 60000ms
           --cluster-pipeline <arg>  # 每次批量迁移键的数量,默认 10
           --cluster-replace

将 9001、9002、9003 的槽迁移到 9007,共迁移 4096 个

$ /usr/local/redis/bin/redis-cli --cluster reshard 192.168.11.40:9001
M: 8ccdb0963411ebd05ce21952bdd4b7597825afdc 192.168.11.40:9001
   slots:[0-4095],[4097-5461] (5461 slots) master
   1 additional replica(s)
M: bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d 192.168.11.40:9007
   slots:[4096] (1 slots) master
...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1: 8ccdb0963411ebd05ce21952bdd4b7597825afdc
Source node #2: 5786e3237c7fa413ed22465d15be721f95e72cfa
Source node #3: 85ceb9826e8aa003169c46fb4ba115c72002d4f9
Source node #4: done
    Moving slot 0 from 8ccdb0963411ebd05ce21952bdd4b7597825afdc
    ...
    Moving slot 12287 from 85ceb9826e8aa003169c46fb4ba115c72002d4f9
Do you want to proceed with the proposed reshard plan (yes/no)? yes
Moving slot 0 from 192.168.11.40:9001 to 192.168.11.40:9007:
...
Moving slot 12287 from 192.168.11.40:9003 to 192.168.11.40:9007:

查看最终结果

192.168.11.40:9007> cluster nodes
8ccdb0963411ebd05ce21952bdd4b7597825afdc 192.168.11.40:9001@19001 master - 0 1620933907753 7 connected 1366-4095 4097-5461
5786e3237c7fa413ed22465d15be721f95e72cfa 192.168.11.40:9002@19002 master - 0 1620933906733 1 connected 6827-10922
85ceb9826e8aa003169c46fb4ba115c72002d4f9 192.168.11.40:9003@19003 master - 0 1620933905000 3 connected 12288-16383
bb1bb0f5f9e0ee67846ba8ec94a38da700e2e80d 192.168.11.40:9007@19007 myself,master - 0 1620933900000 8 connected 0-1365 4096 5462-6826 10923-12287
...

检查节点之间槽的均衡性

$ /usr/local/redis/bin/redis-cli --cluster rebalance 192.168.11.40:9001
...
[OK] All 16384 slots covered.
*** No rebalancing needed! All nodes are within the 2.00% threshold.

迁移之后所有主节点负责的槽数量差异在 2% 以内,因此集群节点数据相对均匀,无需调整

二、集群收缩

深入浅析Redis 集群伸缩原理

1. 迁移槽

执行 reshard 三次,将数据平均分布到其他三个节点

2. 忘记节点

60s 内对所有节点执行如下操作:(不建议)

# 执行后,会将该节点加入禁用列表(持续 60s),不再向其发送 Gossip 消息
cluster forget {nodeId}

建议使用 redis-cli 的 del-node 忘记节点:

/usr/local/redis/bin/redis-cli --cluster del-node {host:port} {nodeId}

内部伪代码

def delnode_cluster_cmd(downNode):
    # 下线节点不允许包含slots
    if downNode.slots.length != 0
        exit 1
    end
    # 向集群内节点发送cluster forget
    for n in nodes:
        if n.id == downNode.id:
            # 不能对自己做forget操作
            continue;
        # 如果下线节点有从节点则把从节点指向其他主节点
        if n.replicate && n.replicate.nodeId == downNode.id :
            # 指向拥有最少从节点的主节点
            master = get_master_with_least_replicas();
            n.cluster("replicate",master.nodeId);
        #发送忘记节点命令
        n.cluster('forget',downNode.id)
    # 节点关闭
    downNode.shutdown();

若主从节点都要下线,先下线从,避免全量复制

以上就是Redis 集群伸缩原理的详细内容,更多关于Redis 集群原理的资料请关注三水点靠木其它相关文章!

Redis 相关文章推荐
Redis IP地址的绑定的实现
May 08 Redis
压缩Redis里的字符串大对象操作
Jun 23 Redis
Redis 彻底禁用RDB持久化操作
Jul 09 Redis
Window server中安装Redis的超详细教程
Nov 17 Redis
一文搞懂Redis中String数据类型
Apr 03 Redis
 Redis 串行生成顺序编码的方法实现
Apr 03 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 Redis
Redis特殊数据类型HyperLogLog基数统计算法讲解
Jun 01 Redis
Redis特殊数据类型Geospatial地理空间
Jun 01 Redis
Redis实现订单过期删除的方法步骤
Jun 05 Redis
如何使用注解方式实现 Redis 分布式锁
Jul 23 Redis
Redis延迟队列和分布式延迟队列的简答实现
基于Redis延迟队列的实现代码
基于Redis实现分布式锁的方法(lua脚本版)
redis三种高可用方式部署的实现
May 11 #Redis
Redis数据结构之链表与字典的使用
基于Redis位图实现用户签到功能
May 08 #Redis
基于Redis过期事件实现订单超时取消
May 08 #Redis
You might like
一键删除顽固的空文件夹 软件下载
2007/01/26 PHP
php文章内容分页并生成相应的htm静态页面代码
2010/06/07 PHP
PHP空值检测函数与方法汇总
2017/11/19 PHP
js 获取服务器控件值的代码
2010/03/05 Javascript
javascript代码加载优化方法
2011/01/30 Javascript
Node.js 异步编程之 Callback介绍(一)
2015/03/30 Javascript
JavaScript实现的圆形浮动标签云效果实例
2015/08/06 Javascript
详解jQuery UI库中文本输入自动补全功能的用法
2016/04/23 Javascript
5个最顶级jQuery图表类库插件【jquery插件库】
2016/05/05 Javascript
JS数组操作(数组增加、删除、翻转、转字符串、取索引、截取(切片)slice、剪接splice、数组合并)
2016/05/20 Javascript
JQuery点击事件回到页面顶部效果的实现代码
2016/05/24 Javascript
jQuery+css实现非常漂亮的水平导航菜单效果
2016/07/27 Javascript
Bootstrap php制作动态分页标签
2016/12/23 Javascript
jQuery基于正则表达式的表单验证功能示例
2017/01/21 Javascript
Node.js的特点详解
2017/02/03 Javascript
利用jQuery实现滑动开关按钮效果(附demo源码下载)
2017/02/07 Javascript
js中删除数组中的某一元素实例(无下标时)
2017/02/28 Javascript
Angular1.x复杂指令实例详解
2017/03/01 Javascript
利用JavaScript如何查询某个值是否数组内
2017/07/30 Javascript
js实现图片粘贴上传到服务器并展示的实例
2017/11/08 Javascript
nodejs基础之buffer缓冲区用法分析
2018/12/26 NodeJs
微信小程序 WXML节点信息查询详解
2019/07/29 Javascript
node.js文件操作系统实例详解
2019/11/05 Javascript
[01:11:35]Liquid vs LGD 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
python实战之实现excel读取、统计、写入的示例讲解
2018/05/02 Python
Python生成短uuid的方法实例详解
2018/05/29 Python
Python动态赋值的陷阱知识点总结
2019/03/17 Python
python适合人工智能的理由和优势
2019/06/28 Python
python使用 request 发送表单数据操作示例
2019/09/25 Python
Python编程快速上手——Excel表格创建乘法表案例分析
2020/02/28 Python
详解pandas绘制矩阵散点图(scatter_matrix)的方法
2020/04/23 Python
详解numpy1.19.4与python3.9版本冲突解决
2020/12/15 Python
关于HTML5你必须知道的28个新特性,新技巧以及新技术
2012/05/28 HTML / CSS
党员评议思想汇报
2014/10/08 职场文书
民间借贷借条如何写
2015/05/26 职场文书
python 字典和列表嵌套用法详解
2021/06/29 Python