总结Redis Cluster原理+基本使用+运维注意事项_redis cluster 使用(1)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

(三)集群命令

四、集群伸缩原理与实现

(一)伸缩原理本质 :集群伸缩=槽和数据在节点之间的移动

(二)扩容集群

1.使用redis-trib.rb工具准备加入新节点并加入集群

2.迁移槽和数据

3.添加从节点

(三)缩容集群

1.安全下线节点流程

2.下线迁移槽

3.忘记节点

五、请求路由原理及集群客户端的选择

(一)请求重定向

1.计算槽

2.槽节点查找

(二)smart客户端原理

(三)Smart客户端——JedisCluster

1.JedisCluster的定义

2.多节点命令和操作

3.批量操作的方法

4.使用Lua、事务等特性的方法

(四)ASK重定向

1.客户端ASK重定向流程

2.节点内部处理

六、故障转移及注意事项

(一)故障发现

1.主观下线

2.客观下线:

3.尝试客观下线

(二)故障恢复

1.资格检查

2.准备选举时间

3.发起选举

4.选举投票

5.替换主节点

七、集群运维的几大注意点

注意点1:集群完整性

注意点2:带宽消耗

注意点3:pub/sub 广播

注意点4:集群倾斜:内存不均匀

1.数据倾斜

2.请求倾斜

注意点4:读写分离:很复杂,成本很高,一般不建议使用

注意点5:数据迁移

注意点6:集群限制(前面也讲过)

注意点7:各种运维坑及解决方案

参考书籍、文献和资料:


一、Redis Cluster数据分布理论选择

(一)数据分布关注点

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据划分到多个节点上,每个节点负责整体数据的一个子集。重点关注与解决的主要是数据分区规则,常见的分区规则则有哈希分区和顺序分区两种,分布式存储数据分区如下图:

哈希分区与顺序分区的主要对比如下表:

分区方式特点代表产品
哈希分区离散度好Redis Cluster
数据分区与业务无关Cassandra
无法顺序访问Dynamo
顺序分区离散度容易倾斜Bigtable
数据分布业务相关HBase
可顺序访问Hypertable

由表可见Redis Cluster采用的是哈希分区,可行方法主要有以下三种方案,综合考虑,Redis Cluster采用的是虚拟槽分区方案。

(二)三种数据分布方案的对比

节点取余分区方案

实现思路

使用特定的数据(Redis的健或用户ID),根据节点数量N使用公式计算哈希值:hash(key)%N,决定数据映射到某一节点。

优点

简单,常用于数据库的分库分表规则,一般采用预分区的方式,提前根据数据量规划好分区数(eg划分为512或1024张表),保证可支撑未来一段时间的数据量,在根据负载情况将表迁移到其他数据库中。要求扩容时采用范培扩容,避免数据映射全部被打乱导致全量迁移的情况。

缺点

当节点数量变化时,即扩容或缩容节点时,数据节点映射关系需要重新计算,会导致数据的重新迁移。

具体分析案例如下图:

解决方案:翻倍扩容可以使数据迁移从80%降到50%

一致性哈希分区方案

实现思路

为系统的每个节点分配一个token,范围一般在0 ~ 2^{32} ,这些toke构成一个哈希环,数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点。

优点

加入和删除节点只影响哈希环中相邻的节点。

缺点

  • 加减节点会造成哈希环中部分数据无法命中,所以应用于缓存场景是可以容忍的;
  • 不适合少量数据节点的分布式方案;
  • 普通一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。
虚拟槽分区方案(Redis Cluster采用此方案)

实现思路

虚拟分槽使用良好的哈希函数把所有数据映射到一个固定范围(这个范围远远大于节点数,比如redisCluster槽的范围是0~16383)的整数集合中,整数定义为槽(slot)。槽是集群内数据管理和迁移的基本单位。使用大范围槽的主要目的是为了方便数据拆分和集群扩展,要求每一个节点负责维护一部分槽以及所映射的键值数据。

基本计算公式为:slot=CRC16(key)&16383,具体如下图:

优点

  • 解耦数据和节点之间的关系,简化了节点扩容和收缩的难度;
  • 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据;
  • 支持节点,槽,键之间的映射关系,用于数据路由、在线伸缩等场景。

缺点:也就是集群功能限制的地方

  • key批量操作支持有限。对于映射为不同slot值的key由于执行mset、mget等操作可能存在于多个节点上而不被支持。
  • key事务操作支持有限。多个key分布在不同节点上时无法使用事务功能。
  • key作为数据分区的最小粒度,不能将一个大的键值对象如hash、list等映射到不同的节点。
  • 不支持多数据库空间。单机下Redis可以支持16个数据库,集群模式下只能使用一个数据库空间,即db0。
  • 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。

二、Redis Cluster节点通信概述

在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态信息。

常见的元数据维护方式分为:集中式和P2P方式。

Redis集群采用P2P的Gossip(流言)协议,Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息,这种方式类似流言传播。

(一)Gossip消息

Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息,常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息。

(二)消息格式分析

消息格式划分:(具体源码可自行查看)

1.消息头
  • 消息头包含发送节点自身状态数据,接收节点根据消息头就可以获取到发送节点的相关数据;
  • 集群内所有的消息都采用相同的消息头结构clusterMsg,它包含了发送节点关键信息,如节点id、槽映射、节点标识(主从角色,是否下线)等。
2.消息体
  • 消息体在Redis内部采用clusterMsgData结构声明。
  • 消息体clusterMsgData定义发送消息的数据,其中ping、meet、pong都采用cluster MsgDataGossip数组作为消息体数据,实际消息类型使用消息头的 type 属性区分。每个消息体包含该节点的多个clusterMsgDataGossip结构数据,用于信息交换。

(三)消息处理流程

接收节点收到ping/meet消息时,执行解析消息头和消息体流程:

解析消息头过程

消息头包含了发送节点的信息,如果发送节点是新 节点且消息是meet类型,则加入到本地节点列表;如果是已知节点,则尝试更新发送节点的状态,如槽映射关系、主从角色等状态。

解析消息体过程

如果消息体的clusterMsgDataGossip数组包含的节点是新节点,则尝试发起与新节点的meet握手流程;如果是已知节点,则根据 cluster MsgDataGossip中的flags字段判断该节点是否下线,用于故障转移。
消息处理完后回复pong消息,内容同样包含消息头和消息体,发送节点接收到回复的pong消息后,采用类似的流程解析处理消息并更新与接收节点最后通信时间,完成一次消息通信。

(四)节点选择

Redis集群的Gossip协议需要兼顾信息交换实时性和成本开销,通信节点选择的规则如图所示:

根据通信节点选择的流程可以看出消息交换的成本主要体现在单位时间选择发送消息的节点数量和每个消息携带的数据量。

选择发送消息的节点数量:

  • 每个节点每秒需要发送ping消息的数量=1+10*num(node.pong_received>cluster_node_timeout/2),因此 cluster_node_timeout参数对消息发送的节点数量影响非常大。
  • 当我们的带宽资源紧张时,可以适当调大这个参数,如从默认15秒改为30秒来降低带宽占用率。
  • 过度调大cluster_node_timeout会影响消息交换的频率从而影响故障转移、槽信息更新、新节点发现的速度。
  • 需要根据业务容忍度和资源消耗进行平衡,同时整个集群消息总交换量也跟节点数成正比。

消息数据量:

  • 每个ping消息的数据量体现在消息头和消息体中,其中消息头主要占用 空间的字段是myslots[CLUSTER_SLOTS/8],占用2KB,这块空间占用相对固定。消息体会携带一定数量的其他节点信息用于信息交换。
  • 消息体携带数据量跟集群的节点数息息相关,更大的集群每次消息通信的成本也就更高,因此对于Redis集群来说并不是大而全的集群更好。

(五)通信流程总述

  • 集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000
  • 每个节点在固定周期内通过特定规则选择几个节点发送ping消息
  • 接收到ping消息的节点用pong消息作为响应

集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息变更等事件发生时,通过不断的ping/pong消息通信,经过一段时间后所有的节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。

三、搭建集群与简单使用

搭建集群工作需要基本的三步:准备节点–节点握手–分配槽。其基本的手动搭建可自行了解

(一)环境准备

采用的Vmware pro 12虚拟机,创建一个centos7最小化版本节点,在上面安装redis-4.0.6,再复制两个redis实例。然后通过vmware克隆一个新的节点。从而保证三主三从共6个redis实例。

(二)操作步骤

1.在每台centos机器的/usr/local文件夹下创建redis-cluster文件夹作为redis集群根目录

#cd /usr/local && mkdir redis-cluster

2.在redis-cluster文件夹下安装3个redis实例,主要是将源码包里的redis.conf拷贝过来,修改几个参数,如下

port  7000               //自定义每个redis实例端口如7000~7006 
protected-mode no        //默认保护模式yes,修改为no       
#bind 127.0.0.1          //默认安全保护,只能访问本机
daemonize    yes         //redis后台运行
cluster-enabled  yes     //开启集群  把注释#去掉
#下面项实验时未进行设置
pidfile  ./redis_7000.pid     //pidfile文件对应7000,7001,7002
cluster-config-file  nodes_7000.conf   //集群的配置  配置文件首次启动自动生成 7000,7001,7002
cluster-node-timeout  15000      //请求超时  默认15秒,可自行设置
appendonly  yes                  //aof日志开启  有需要就开启,它会每次写操作都记录一条日志

3.将源码包里的ruby脚本redis-trib.rb拷贝到redis-cluster文件夹下

#cp redis-trib.rb /usr/local/redis-cluster/

4.安装ruby环境

#yum -y install ruby ruby-devel rubygems rpm-build
#gem install redis

5.启动每个redis实例

#redis-server redis.conf
ps 检查进程: ps -ef|grep redis

6.执行ruby脚本

#redis-trib.rb create --replicas 1 192.168.1.80:7000 192.168.1.80:7001 192.168.1.80:7002 192.168.1.81:7003 192.168.1.81:7004 192.168.1.81:7005

(三)集群命令

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

1.创建集群

#redis-trib.rb create --replicas 1 192.168.1.80:7000 192.168.1.80:7001 192.168.1.80:7002 192.168.1.81:7003 192.168.1.81:7004 192.168.1.81:7005
其中:create //表示创建集群功能
--replicas 1 //表示为每个主节点自动分配一个从节点.也就是自动分配三个主节点和三个从节点.

2.查看集群状态 :登录客户端 redis-cli -p 7002(可以任意一个)

#cluster info 

3.查看集群节点信息 :登录客户端 redis-cli -p 7002(可以任意一个)

#cluster nodes

4.检查集群状态

#redis-cli -c -p 7002 cluster nodes

5.增加redis节点

  1. 创建两个实例目录,一个实例做为新节点的主实例,一个实例做为新节点的从实例 
    2)修改相应的redis.conf,修改端口等信息 
    3)启动这两个实例 
    4)增加节点
#redis-trib.rb add-node 192.168.1.81:7007 192.168.1.80:7001
PS:这个IP:PORT可以是集群里边儿任意一个主节点的IP和端口

#redis-cli -c -p 7002 cluster nodes
36d53c7f1896838249c0b4afdcf680bac2f4ec2e 192.168.1.81:7007 master - 0 1463476564369 0 connected

发现7007这个实例已经做为主节点加到集群里边儿来了.

5)7008做为7007的从节点也加入到集群里边儿来, 注意记住7007这个主节点的节点id.从节点加入到集群的时候要用到.

#redis-trib.rb add-node --slave --master-id 36d53c7f1896838249c0b4afdcf680bac2f4ec2e 192.168.1.81:7008 192.168.1.80:7001

6)重新分片

#redis-trib.rb reshard 192.168.1.80:7001
//PS: 这条命令是交互的,按照提示操作即可.
How many slots do you want to move (from 1 to 16384)?4096  //输入一个数,这个4096表示迁移多少个slots数
What is the receiving node ID? 36d53c7f1896838249c0b4afdcf680bac2f4ec2e //输入目标节点ID,表示迁移到哪个目标节点
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:all //输入all表示从老的所有节点进行重分配,凑够4096个slots给到新节点.
也可以输入源节点id, 可以输入多个源节点id,最后输入done.就开始从你输入的源节点id的节点进行迁移了.

6.删除redis节点(主节点 7001) 
首先必须确保这个节点没有拥有任何一个slots 
1)查看集群节点信息

#redis-cli -c -p 7002 cluster nodes
a2eee0ea546f2c3701b08981737c07938039857c 192.168.1.80:7001 master - 0 1463477001334 1 connected 1365-5460

2)重新分片

#redis-trib.rb reshard 192.168.1.80:7001
How many slots do you want to move (from 1 to 16384)?16384 //输入一个大于或等于7001节点所拥有的slots数的数即可.
What is the receiving node ID? 8ab3d14eba181c06dc8826bea0db1becdead2533 //接收这些slots的目标节点,这里是7002节点
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:a2eee0ea546f2c3701b08981737c07938039857c //因为我们要删除7001这个节点,所以源节点的id就是7001的节点ID
Source node #2:done //输入done,回车,就会开始从7001 这个节点迁移16384个slot(没有这么多就迁移拥有的全部)到7002节点中去.

再看各个节点的状态

#redis-cli -c -p 7002 cluster nodes 
a2eee0ea546f2c3701b08981737c07938039857c 192.168.1.80:7001 master - 0 1463477349186 1 connected

#redis-trib.rb del-node 192.168.1.80:7002 a2eee0ea546f2c3701b08981737c07938039857c      
ps: 这个主节点被删除之后,它之前拥有的从节点会自动成为其他主节点的从节点               

四、集群伸缩原理与实现

理解集群的水平伸缩的上层原理:集群伸缩=槽和数据在节点之间的移动

(一)伸缩原理本质 :集群伸缩=槽和数据在节点之间的移动

Redis 集群提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容,对节点进行灵活上下线控制,原理可抽象为槽和对应数据在不同节点之间灵活移动。

以下图为例,三个主节点分别维护自己负责的槽和对应的数据,如果希望加入1个节点实现集群扩容时,需要通过相关命令把一部分槽和数据迁移给新节点,图中每个节点把一部分槽和数据迁移到新的节点6385,每个节点负责的槽和数据相比之前变少了从而达到了集群扩容的目的。

(二)扩容集群

Redis 集群扩容操作可分为以下步骤:准备新节点–>加入集群–>迁移槽和数据。

1.使用redis-trib.rb工具准备加入新节点并加入集群

redis-trib.rb工具也实现了为现有集群添加新节点的命令,还实现了直接添加为从节点的支持,命令如下:

#redis-trib.rb add-node new_host:new_port existing_host:existing_port --slave
   --master-id <arg>

其内部同样采用cluster meet命令实现加入集群功能。加入集群操作可以采用如下命令实现新节点加入:

#redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
#redis-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379

其原理基本如下:

2.迁移槽和数据

加入集群后需要为新节点迁移槽和相关数据,槽在迁移过程中集群可以正常提供读写服务,迁移过程是集群扩容最核心的环节,下面详细讲解。

槽迁移计划

槽是Redis集群管理数据的基本单位,首先需要为新节点制定槽的迁移计划,确定原有节点的哪些槽需要迁移到新节点。迁移计划需要确保每个节点负责相似数量的槽,从而保证各节点的数据均匀。

例如,在集群中加入6385节点,如下图所示(新节点加入的槽迁移计划)。加入6385节点后,原有节点负责的槽数量从6380变为4096个。槽迁移计划确定后开始逐个把槽内数据从源节点迁移到目标节点,如下图所示(槽和数据迁移到6385节点)。

迁移数据

数据迁移过程是逐个槽进行的,其槽和数据迁移流程及伪代码模拟迁移过程如下图:

每个槽数据迁移的流程如下:

  • 对目标节点发送cluster setslot {slot } importing {sourceNodeId}命令,让目标节点准备导入槽的数据。
  • 对源节点发送cluster setslot {slot} migrating {targetNodeId}命令,让源节点准备迁出槽的数据。
  • 源节点循环执行cluster getkeysinslot {slot} {count}命令,获取count个属于槽{slot }的键。
  • 在源节点上执行migrate {targetIp} {targetPort} ""0 {t imeout} keys {keys…}命令,把获取的键通过流水线(pipeline)机制批量迁移到目标节点,批量迁移版本的m igrate命令在Redis3.0.6以上版本提供,之前的migrate命令只能单个键迁移。对于大量key 的场景,批量键迁移将极大降低节点之间网络IO次数。
  • 重复执行步骤3和步骤4直到槽下所有的键值数据迁移到目标节点。
  • 向集群内所有主节点发送cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点。为了保证槽节点映射变更及时传播,需要遍历发送给所有主节点更新被迁移的槽指向新节点。

redis-trib提供了槽重分片功能,命令如下:

redis-trib.rb reshard host:port --from <arg> --to <arg> --slots <arg> --yes --timeout
	<arg> --pipeline <arg>

参数说明:

  • host:port:必传参数,集群内任意节点地址,用来获取整个集群信息。
  • from :制定源节点的id,如果有多个源节点,使用逗号分隔,如果是all源节点变为集群内所有主节点,在迁移过程中提示用户输入。
  • to:需要迁移的目标节点的id,目标节点只能填写一个,在迁移过程中提示用户输入。
  • slots:需要迁移槽的总数量,在迁移过程中提示用户输入。
  • yes:当打印出reshard执行计划时,是否需要用户输入yes确认后再执行reshard。
  • timeout:控制每次migrate操作的超时时间,默认为60000毫秒。
  • pipeline:控制每次批量迁移键的数量,默认为10。

reshard命令简化了数据迁移的工作量,其内部针对每个槽的数据迁移同样使用之前的流程。我们已经为新节点6395迁移了一个槽4096,剩下的槽数据迁移使用redis-trib.rb完成,命令如下:

#redis-trib.rb reshard 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379
slots:0-4095,4097-5461 (5461 slots) master
1 additional replica(s)
M: 40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
1 additional replica(s)
M: 8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380
slots:5462-10922 (5461 slots) master
1 additional replica(s)
M: 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 127.0.0.1:6385
slots:4096 (1 slots) master
0 additional replica(s)
// ...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

打印出集群每个节点信息后,reshard命令需要确认迁移的槽数量,这里我们输入4096个:

How many slots do you want to move (from 1 to 16384)4096

输入6385的节点ID作为目标节点,目标节点只能指定一个:

What is the receiving node ID 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756

之后输入源节点的ID,这里分别输入节点6379、6380、6381三个节点ID最后用done表示结束:

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:cfb28ef1deee4e0fa78da86abe5d24566744411e
Source node #2:8e41673d59c9568aa9d29fb174ce733345b3e8f1
Source node #3:40b8d09d44294d2e23c7c768efc8fcd153446746
Source node #4:done

数据迁移之前会打印出所有的槽从源节点到目标节点的计划,确认计划无误后输入yes执行迁移工作:

Moving slot 0 from cfb28ef1deee4e0fa78da86abe5d24566744411e
....
Moving slot 1365 from cfb28ef1deee4e0fa78da86abe5d24566744411e
Moving slot 5462 from 8e41673d59c9568aa9d29fb174ce733345b3e8f1
...
Moving slot 6826 from 8e41673d59c9568aa9d29fb174ce733345b3e8f1
Moving slot 10923 from 40b8d09d44294d2e23c7c768efc8fcd153446746
...
Moving slot 12287 from 40b8d09d44294d2e23c7c768efc8fcd153446746
Do you want to proceed with the proposed reshard plan (yes/no) yes

redis-trib工具会打印出每个槽迁移的进度,如下:

Moving slot 0 from 127.0.0.1:6379 to 127.0.0.1:6385 ....
....
Moving slot 1365 from 127.0.0.1:6379 to 127.0.0.1:6385 ..
Moving slot 5462 from 127.0.0.1:6380 to 127.0.0.1:6385: ....
....
Moving slot 6826 from 127.0.0.1:6380 to 127.0.0.1:6385 ..
Moving slot 10923 from 127.0.0.1:6381 to 127.0.0.1:6385 ..
...
Moving slot 10923 from 127.0.0.1:6381 to 127.0.0.1:6385 ..

当所有的槽迁移完成后,reshard命令自动退出,执行cluster nodes命令检查节点和槽映射的变化,如下所示:

127.0.0.1:6379>cluster nodes
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 slave cfb28ef1deee4e0fa
	78da86abe5d24566744411e 0 1469779084518 3 connected
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0
	1469779085528 2 connected 12288-16383
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 slave 40b8d09d44294d2e2
	3c7c768efc8fcd153446746 0 1469779087544 5 connected
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 slave 8e41673d59c9568aa
	9d29fb174ce733345b3e8f1 0 1469779088552 4 connected
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0
	connected 1366-4095 4097-5461
475528b1bcf8e74d227104a6cf1bf70f00c24aae 127.0.0.1:6386 master - 0
1469779086536 8 connected
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0
	1469779085528 1 connected 6827-10922
1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 127.0.0.1:6385 master - 0
	1469779083513 9 connected 0-1365 4096 5462-6826 10923-12287

点6385负责的槽变为:0-136540965462-682610923-12287。由于槽用于hash 运算本身顺序没有意义,因此无须强制要求节点负责槽的顺序性。迁移之后建议使用redis-trib.rb rebalance命令检查节点之间槽的均衡性。命令如下:

# redis-trib.rb rebalance 127.0.0.1:6380
>>> Performing Cluster Check (using node 127.0.0.1:6380)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
*** No rebalancing needed! All nodes are within the 2.0% threshold.

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

3.添加从节点

扩容之初我们把6385、6386节点加入到集群,节点6385迁移了部分槽和数据作为主节点,但相比其他主节点目前还没有从节点,因此该节点不具备故障转移的能力。

这时需要把节点6386作为6385的从节点,从而保证整个集群的高可用。使用cluster replicate {masterNodeId}命令为主节点添加对应从节点,注意在集群模式下slaveof添加从节点操作不再支持。如下所示:

127.0.0.1:6386>cluster replicate 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756

从节点内部除了对主节点发起全量复制之外,还需要更新本地节点的集群相关状态,查看节点6386状态确认已经变成6385节点的从节点:

127.0.0.1:6386>cluster nodes
475528b1bcf8e74d227104a6cf1bf70f00c24aae 127.0.0.1:6386 myself,slave 1a205dd8b2
	819a00dd1e8b6be40a8e2abe77b756 0 0 8 connected
1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 127.0.0.1:6385 master - 0 1469779083513 9
	connected 0-1365 4096 5462-6826 10923-12287
...

到此整个集群扩容完成,集群关系结构如下图所示(扩容后集群结构)。

(三)缩容集群

1.安全下线节点流程

收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点。安全下线节点流程如下图所示(节点安全下线流程):

  • 首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
  • 当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。

2.下线迁移槽

下线节点需要把自己负责的槽迁移到其他节点,原理与之前节点扩容的迁移槽过程一致。例如我们把6381和6384节点下线,节点信息如下:

127.0.0.1:6381> cluster nodes
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 myself,master - 0 0 2 connected
	12288-16383
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 slave 40b8d09d44294d2e2
	3c7c768efc8fcd153446746 0 1469894180780 5 connected
...

6381是主节点,负责槽(12288-16383),6384是它的从节点,如上图所示(迁移下线节点6381的槽和数据)。下线6381之前需要把负责的槽迁移到其他节点。

收缩正好和扩容迁移方向相反,6381变为源节点,其他主节点变为目标节点,源节点需要把自身负责的4096个槽均匀地迁移到其他主节点上。这里直接使用redis-trib.rb reshard命令完成槽迁移。由于每次执行reshard命令只能有一个目标节点,因此需要执行3次reshard命令,分别迁移1365、1365、1366个槽,如下所示:

#redis-trib.rb reshard 127.0.0.1:6381
>>> Performing Cluster Check (using node 127.0.0.1:6381)
...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)1365
What is the receiving node ID cfb28ef1deee4e0fa78da86abe5d24566744411e /*输入6379
	节点id作为目标节点.*/
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:40b8d09d44294d2e23c7c768efc8fcd153446746 /*源节点6381 id*/
Source node #2:done /* 输入done确认 */
...
Do you want to proceed with the proposed reshard plan (yes/no) yes
...

槽迁移完成后,6379节点接管了1365个槽12288~13652,如下所示:

127.0.0.1:6379> cluster nodes
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 10 connected
1366-4095 4097-5461 12288-13652
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1469895725227 2
connected 13653-16383
...

继续把1365个槽迁移到节点6380:

#redis-trib.rb reshard 127.0.0.1:6381
>>> Performing Cluster Check (using node 127.0.0.1:6381)
...
How many slots do you want to move (from 1 to 16384) 1365
What is the receiving node ID 8e41673d59c9568aa9d29fb174ce733345b3e8f1 /*6380节点
作为目标节点.*/
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:40b8d09d44294d2e23c7c768efc8fcd153446746
Source node #2:done
...
Do you want to proceed with the proposed reshard plan (yes/no)yes
...

完成后,6380节点接管了1365个槽13653~15017,如下所示:

127.0.0.1:6379> cluster nodes
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1469896123295 2
connected 15018-16383
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1469896125311 11
connected 6827-10922 13653-15017
...

把最后的1366个槽迁移到节点6385中,如下所示:

#redis-trib.rb reshard 127.0.0.1:6381
...
How many slots do you want to move (from 1 to 16384) 1366
What is the receiving node ID 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 /*6385
节点id作为目标节点.*/
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:40b8d09d44294d2e23c7c768efc8fcd153446746
Source node #2:done
...
Do you want to proceed with the proposed reshard plan (yes/no) yes
...

到目前为止,节点6381所有的槽全部迁出完成,6381不再负责任何槽。状态如下所示:

127.0.0.1:6379> cluster nodes
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1469896444768 2
connected
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1469896443760 11
connected 6827-10922 13653-15017
1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 127.0.0.1:6385 master - 0 1469896445777 12
connected 0-1365 4096 5462-6826 10923-12287 15018-16383
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 10 connected
1366-4095 4097-5461 12288-13652
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 slave 8e41673d59c9568aa9d29fb17
4ce733345b3e8f1 0 1469896444264 11 connected
...

下线节点槽迁出完成后,剩下的步骤需要让集群忘记该节点。

3.忘记节点

由于集群内的节点不停地通过Gossip消息彼此交换节点状态,因此需要通过一种健壮的机制让集群内所有节点忘记下线的节点。也就是说让其他节点不再与要下线节点进行Gossip消息交换。Redis提供了cluster forget {downNodeId}命令实现该功能,如下图所示(在有效期60秒内对所有节点执行cluster forget操作)。

当节点接收到cluster forget {downNodeId}命令后,会把n odeId指定的节点加入到禁用列表中,在禁用列表内的节点不再发送Gossip消息。禁用列表有效期是60秒,超过60秒节点会再次参与消息交换。也就是说当第一次forget命令发出后,我们有60秒的时间让集群内的所有节点忘记下线节点。

线上操作不建议直接使用cluster forget命令下线节点,需要跟大量节点命令交互,实际操作起来过于繁琐并且容易遗漏forget节点。建议使用redist rib.rb del-node {host:port} {downNodeId}命令,内部实现的伪代码如下:

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();

从伪代码看出del -node命令帮我们实现了安全下线的后续操作。当下线主节点具有从节点时需要把该从节点指向到其他主节点,因此对于主从节点都下线的情况,建议先下线从节点再下线主节点,防止不必要的全量复制。对于6381和6384节点下线操作,命令如下:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值