目录
一 理论基础
1.1 redis的数据分区
1.2 集群功能限制
二 搭建
三 节点通信
3.1通信流程
3.2 Gossip消息
3.3节点选择
四 集群伸缩
4.1扩容集群
4.2收缩集群
五 请求路由
5.1请求重定向
5.2 smart客户端
5.3ASK重定向
redis3.0推出了分布式解决方案 redis Cluster;
当单机遇到了内存、并发、流量等瓶颈时,可以采用Cluster架构方案来达到负载均衡目的。
一 理论基础
分布式数据库首先要解决如何把整个数据集按照分区规则映射到多个节点上,每个节点负责整体数据的一个子集。
常见的分区规则有哈希分区和顺序分区两种。

下面介绍集中常见的哈希分区规则;
- 节点取余分区
使用特定的数据,如Redis的键或用户ID,再根据节点数量N使用公式: hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。
缺点:当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移。
优点: 这种方式的突出优点是简单,易理解;
这种方式常用于数据库的分库分表规则,一般采 用预分区的方式,提前根据数据量规划好分区数,比如划分为512或1024张 表,保证可支撑未来一段时间的数据量,再根据负载情况将表迁移到其他数 据库中。
扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移 的情况
- 一致性哈希分区
一致性哈希分区(Distributed Hash Table)实现思路是为系统中每个节点分配一个token(范围一般在0~2^32),这些token构成一个哈希环。
数据读写 执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点;
缺点:
加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此 这种方式不适合少量数据节点的分布式方案。
优点:相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响
- 虚拟槽分区
虚拟槽分区使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot);
槽范围一般远远大于节点数,比如Redis Cluster槽范围是0~16383;
槽是集群内数据 管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集 群扩展;
每个节点会负责一定数量的槽;
1.1 redis的数据分区
Redis Cluser 采用虚拟槽分区,所有的键根据哈希函数映射到 0~16383整 数槽内;
计算公式:slot=CRC16( key ) &16383;
每一个节点负责维护一部分槽以及槽所映射的键值数据;
redis虚拟槽分区的特点:
1.2 集群功能限制
- key批操作命令支持有限。如mset、mget,目前只支持具有相同slot值的 key执行批量操作。
- key事务操作支持有限。只支持多key在同一节点上的事务操 作,当多个key分布在不同的节点上时无法使用事务功能。
- key作为数据分区的最小粒度,因此不能将一个大的键值对象如 hash、list等映射到不同的节点。
- 不支持多数据库空间。单机下的Redis可以支持16个数据库,集群模 式下只能使用一个数据库空间,即db0。
- 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复 制结构。
二 搭建
搭建集群工作分四步:
- 准备节点
Redis 集群一般由多个节点组成,节点数量至少为 6个才能保证组成完整高可用的集群。
cluster-enabled : 每个节点需要开启配置 cluster-enabled yes ,让 Redis运行在集群模式下。
cluster-config-file: 第一次启动时如果没有【集群配置文件】,它会自动创建一份,该配置文件名称采用cluster-config-file配置参数项控制;
节点重启的时候 会加载该配置文件进行重用;
如果集群内节点信息发生变化,如添加节点、节点下线、故障转移等。节点会自动保存集群状态到配置文件中( cluster-config-file)。
Redis自动维护集群配置文件,不要手动修改,防止节点重启时产生集群信息错乱。
- 节点握手
节点握手是指一批运行在集群模式下的节点通过 Gossip协议彼此通信, 达到感知对方的目的。
由客户端使用命令:
cluster meet{ip}{port}
cluster meet命令是一个异步命令,执行之后立刻返回。内部发起与目标节点进行握手通信;
我们只需要在集群内任意节点上执行cluster meet命令加入新节点,握手 状态会通过消息在集群内传播,这样其他节点会自动发现新节点并发起握手流程。
节点建立握手之后集群还不能正常工作,这时集群处于下线状态,所有的数据读写都被禁止。
由于目前所有的槽没有分配到节点,因此集群无法完成槽到节点的映射。
只有当 16384 个槽全部分配给节点后,集群才进入在线状态。
- 分配槽
通过 cluster addslots命令为节点分配槽
- 设置主从关系
作为一个完整的集群,每个负责处理槽的 节点应该具有从节点,保证当它出现故障时可以自动进行故障转移。
集群模式下, Reids 节点角色分为主节点和从节点。
首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关的数据。
cluster replicate{nodeId}命令让一个节点成为从节点
注意:在群模式下slaveof添加从节点操作不再支持
- cluster info命令可以获取集群当前状态:
cluster nodes命令可以看到节点和槽的分配关系:
行cluster nodes命令可以看到节点和槽的分配关系:
下面按照上述流程 ,在本机环境模拟搭建一套cluster系统
- 准备节点
我在本机准备了6个节点,使用端口46379 ~ 46384模拟6个集群节点;

启动节点: redis-server cluster-46379/conf/redis-46379.conf
redis-server cluster-46380/conf/redis-46380.conf
redis-server cluster-46381/conf/redis-46381.conf
redis-server cluster-46382/conf/redis-46382.conf
redis-server cluster-46383/conf/redis-46383.conf
redis-server cluster-46384/conf/redis-46384.conf
查看是否启动成功:

通过ps命令,可以看到节点都已启动成功,并分别生成了节点配置文件;
如node-46379.conf中内容如下,这里最重要的是节点id,它是一个40位16进制字符串,用于唯一标识集群内一个节点,之后很多集群操作都要借助于节点ID来完成。 >cat nodes-46379.conf
52054185123287182a9fea16bba27f4692f9030a :0@0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
- 节点握手 cluster meet
连接上 46379的服务器执行 cluster meet命令,让节点间通信;
只需要在集群内任意节点上执行cluster meet命令加入新节点,握手状态会通过消息在集群内传播,这样其他节点会自动发现新节点并发起握手流程。

执行cluster info命令查看当集群状态:

由于目前所有的槽没有分配到节点,因此集群无法完成槽到节点的映射。只有当16384 个槽全部分配给节点后,集群才进入在线状态。
- 分配槽cluster addslots
Redis 集群把所有的数据映射到 16384 个槽中。每个key会映射为一个固定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。
通过cluster addslots 命令为节点分配槽。
这里利用 bash 特性批量设置槽( slots)命令如下:
redis-cli -h 127.0.0.1 -p 46379 cluster addslots {0..2730}
redis-cli -h 127.0.0.1 -p 46380 cluster addslots {2731..5461}
redis-cli -h 127.0.0.1 -p 46381 cluster addslots {5462..7211}
redis-cli -h 127.0.0.1 -p 46382 cluster addslots {7212..10922}
redis-cli -h 127.0.0.1 -p 46383 cluster addslots {10923..12331}
redis-cli -h 127.0.0.1 -p 46384 cluster addslots {12332..16383}
至此已经搭建了master -master结构的集群;
三 节点通信
在分布式存储中需要提供维护节点元数据信息的机制。
所谓元数据是 指:节点负责哪些数据,是否出现故障等状态信息。
常见的元数据维护方式 分为:集中式和P2P方式。
Redis集群采用P2P的Gossip(流言)协议, Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息。
3.1通信流程
-
集群中的每个节点都会单独开辟一个 TCP通道,用于节点之间彼此通信,默认通信端口号在基础端口上加 10000(也可通通过 cluter meet [cport] 的cport的参数指定端口) 。
-
每个节点在固定周期内通过特定规则选择几个节点发送 ping 消息。
-
接收到 ping 消息的节点用 pong 消息作为响应。
当节点出故障、新节点加入、主从角色变化、槽信息 变更等事件发生时,通过不断的ping/pong消息通信
经过一段时间后所有的节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。
3.2 Gossip消息
Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的 Gossip 消息;
常用的 Gossip 消息可分为: ping 消息、 pong 消息、 meet 消息、 fail 消息等;
3.3节点选择
虽然 Gossip协议的信息交换机制具有天然的分布式特性,但它是有成本的。
由于内部需要频繁地进行节点信息交换,而 ping/pong消息会携带当前节点和部分其他节点的状态数据,会加重带宽和计算的负担。
因此节点每次选择需 要通信的节点列表变得非常重要;
通信节点选择过多,虽然可以做到信息及时 交换但成本过高。
节点选择过少会降低集群内所有节点彼此信息交换频率, 从而影响故障判定、新节点发现等需求的速度。
1.选择发送消息的节点数量
集群内每个节点维护定时任务默认每秒执行 10 次,每秒会随机选取 5个节点找出最久没有通信的节点发送 ping 消息,用于保证 Gossip信息交换的随机性。
每 100 毫秒都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于 cluster_node_timeout/2 ,则立刻发送ping消息,防止该节点信息太长时间未更新。
因此 cluster_node_timeout参数对消息发送的节点数量影响非常大。
当我们的带宽资源紧张时,可以适当调大这个参数,如从默认 15 秒改为 30秒来降低带宽占用率。
过度调大 cluster_node_timeout 会影响消息交换的频率从而影响故障转移、槽信息更新、新节点发现的速度。
因此需要根据业务容忍度和资源消耗 进行平衡。同时整个集群消息总交换量也跟节点数成正比。
2.消息数据量
每个 ping消息的数据量体现在消息头和消息体中,其中消息头主要占用空间的字段是 myslots[CLUSTER_SLOTS/8] ,占用 2KB,这块空间占用相对固定。
消息体会携带一定数量的其他节点信息用于信息交换。
四 集群伸缩
Redis集群可以实现对节点的灵活上下线控制。其中原理可抽象为槽和对应数据在不同节点之间灵活移动。
4.1扩容集群
扩容可以分为如下步骤:
1.准备新节点
2.加入集群
准备好新节点后,依然采用 cluster meet 命令,将新的节点加入到集群;
新节点加入到集群后,还没有负责的槽,所以还不能接受任何读写操作;对新节点的后续操作一般有两种选择:
(1)作为其他主节点的从节点,负责故障转移
(2)为他迁移槽和数据,实现扩容
注意:
手动执行 cluster meet 命令加入已经存在于其他集群的节点,会造成被加入节点的集群合并到现有集群的情况从而造成数据丢失和错乱,
后果非常严重,线上谨慎操作。
正式环境建议使用redis-trib.rb add-node命令加入新节点,该命令内部会执行新节点状态检查
如果新节点已经加入其他集群或者包含数据,则放弃集群加入操作
3.迁移槽和数据
最后 迁移槽和数据 是扩容最核心的环节,槽在迁移过程中集群仍然可以正常提供读写服务;
槽是Redis集群管理数据的基本单位,首先需要为新节点制定槽的迁移计划,确定原有节点的哪些槽需要迁移到新节点。
迁移计划需要确保每个节点负责相似数量的槽,从而保证各节点的数据均匀。
迁移具体操作步骤如下:
-
对目标节点发送 cluster setslot {slot} importing {sourceNodeId}命令,让目标节点准备导入槽的数据。
-
对源节点发送 cluster setslot {slot} migrating {targetNodeId}命令,让源节点准备迁出槽的数据。
-
源节点循环执行 cluster getkeysinslot {slot} {count} 命令,获取 count个属于槽 {slot} 的键。
-
在源节点上执行migrate{targetIp}{targetPort}""0{timeout}keys{keys...}命令,把获取的键通过流水线( pipeline )机制批量迁移到目标节点。
-
重复执行步骤 3 )和步骤 4)直到槽下所有的键值数据迁移到目标节点。
-
向集群内所有主节点发送 cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点。
4.2收缩集群
收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点;
操作流程如下:
-
确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性。
原理与上面 节点扩容的迁移槽过程一致。
-
当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。
Redis 提供了cluster forget {downNodeId} 命令实现该功能。
当节点接收到 cluster forget{down NodeId} 命令后,会把 nodeId指定的节点加入到禁用列表中,在禁用列表内的节点不再发送 Gossip消息。
禁用列表有效期是 60 秒,超过 60秒节点会再次参与消息交换。
也就是说当第一次forget 命令发出后,我们有 60 秒的时间让集群内的所有节点忘记下线节点。
线上操作不建议直接使用 cluster forget命令下线节点,需要跟大量节点命令交互,
实际操作起来过于繁琐并且容易遗漏 forget 节点。
建议使用redis-trib.rb del-node{host : port} {downNodeId} 命令
五 请求路由
5.1请求重定向
在集群模式下, Redis接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;
否则回复MOVED重定向错误,通知客户端请求正确的节点。 这个过程称为MOVED重定向;
用 redis-cli 命令时,可以加入-c参数支持自动重定向,简化手动发起重定向操作;
redis-cli 自动帮我们连接到正确的节点执行命令,这个过程是在redis-cli内部维护;
实质上是 client 端接到 MOVED 信息之后再次发起请求,并不在Redis节点中完成请求转发;
Redis计算键所对应的槽时
如果键内容包含{和}大括号字符,则计算槽的有效部分是括号内的内容;否则采用键的全内容计算槽。
5.2 smart客户端
Smart 客户端通过在内部维护 slot→node的映射关系,本地就可实现键到节点的查找,从而保证 IO 效率的最大化,
而 MOVED 重定向负责协助 Smart客户端更新 slot→node映射。
对 Smart客户端成本和可能存在的问题:
5.3ASK重定向
Redis 集群支持在线迁移槽( slot )和数据来完成水平伸缩,当slot对应的数据从源节点到目标节点迁移过程中,客户端需要做到智能识别,保证键命令可正常执行。
例如当一个 slot数据从源节点迁移到目标节点时,期间可能出现一部分数据在源节点,而另一部分在目标节点;
当出现上述情况时,客户端键命令执行流程将发生变化:
-
客户端根据本地 slots缓存发送命令到源节点,如果存在键对象则直接执行并返回结果给客户端。
-
如果键对象不存在,则可能存在于目标节点,这时源节点会回复ASK 重定向异常。
格式如下:( error ) ASK{slot}{targetIP} : {targetPort} 。
-
客户端从 ASK 重定向异常提取出目标节点信息,发送 asking命令到目标节点打开客户端连接标识,再执行键命令。
ASK 与 MOVED 虽然都是对客户端的重定向控制,但是有着本质区别。
ASK 重定向说明集群正在进行 slot数据迁移,客户端无法知道什么时候迁移 完成,因此只能是临时性的重定向,客户端不会更新 slots 缓存;
但是MOVED 重定向说明键对应的槽已经明确指定到新的节点,因此需要更新slots缓存;