十、集群

1、概述

Redis Cluster是Redis 的分布式解决方案,在3.0版本正式推出,有效地解决了Redis分布式方面的需求。

Redis在3.0版本之前分布式方案一般有两种:

  • 客户端分区方案,优点是分区逻辑可控,缺点是需要自己处理数据路由、高可用、故障转移等问题。
  • 代理方案,优点是简化客户端分布式逻辑和升级维护便利,缺点是加重架构部署复杂度和性能损耗。

现在官方为我们提供了专有的集群方案: Redis Cluster,它非常优雅地解决了Redis集群方面的问题。

2、数据分布理论

  • 分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集。
  • 常见的分区规则有哈希分区和顺序分区两种。
  • Redis Cluster采用哈希分区规则。
分区方式 特点 代表产品
哈希分区 - 离散度好
- 数据分布业务无关
- 无法顺序访问
Redis Cluster
Cassandra
Dynamo
顺序访问 - 离散度易倾斜
- 数据分布业务相关
- 可顺序访问
Bigtable
HBase
Hypertable

常见的哈希分区规则有几种:

1)节点取余分区

  • 使用特定的数据,如Redis的键或用户ID,再根据节点数量N使用公式:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。
  • 缺点:当节点数量变化时,如扩容或收缩节点,数据节点映射关系需要重新计算,会导致数据的重新迁移。
  • 优点:简单性,常用于数据库的分库分表规则,一般采用预分区的方式,提前根据数据量规划好分区数,比如划分为512或1024张表,保证可支撑未来一段时间的数据量,再根据负载情况将表迁移到其他数据库中。
  • 扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况。

2)一致性哈希分区

  • 一致性哈希分区(Distributed Hash Table)实现思路是为系统中每个节点分配一个token,范围一般在0~2^32,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点。
  • 相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。
  • 一致性哈希分区存在几个问题:
    1. 加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
    2. 当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此这种方式不适合少量数据节点的分布式方案。
    3. 普通的一致性哈希分区在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。
  • 正因为一致性哈希分区的这些缺点,一些分布式系统采用虚拟槽对一致性哈希进行改进,比如Dynamo系统。

3)虚拟槽分区

  • 虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如Redis Cluster槽范围是0~16383。
  • 槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽。
  • Redis Cluster就是采用虚拟槽分区。

3、Redis数据分区

数据分区是分布式存储的核心。

Redis Cluser采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。

Redis虚拟槽分区的特点:

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

4、集群功能限制

Redis集群相对单机在功能上存在一些限制,限制如下:

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

5、搭建集群

搭建集群工作需要以下三个步骤:

  • 准备节点。
  • 节点握手。
  • 分配槽。

6、搭建集群步骤一:准备节点

集群相关配置如下,其他配置和单机模式一致即可,配置文件命名规则redis-{port}.conf
#节点端口
port 6379
# 开启集群模式
cluster enabled yes
# 节点超时时间,单位毫秒
cluster-node-timeout 15000
# 集群内部配置文件
cluster config file "nodes 6379.conf"#cat data/nodes-6379.conf
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 co vars currentEpoch 0 lastVoteEpoch 0127.0.0.1:6380> cluster nodes       // 每个节点目前只能识别出自己的节点信息(要通过节点握手才能建立联系)。
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 myself,master - 0 0 0 co
  • Redis集群一般由多个节点组成,节点数量至少为6个才能保证组成完整高可用的集群。
  • 每个节点需要开启配置cluster-enabled yes,让Redis运行在集群模式下。
  • 建议为集群内所有节点统一目录,一般划分三个目录:conf、data、log,分别存放配置、数据和日志相关文件。把6个节点配置统一放在conf目录下。
  • 第一次启动时如果没有集群配置文件,它会自动创建一份,文件名称采用cluster-config-file参数项控制,建议采用node-{port}.conf格式定义,通过使用端口号区分不同节点,防止同一机器下多个 节点彼此覆盖,造成集群信息异常。
  • 如果启动时存在集群配置文件,节点会使用配置文件内容初始化集群信息。
  • 集群模式的Redis除了原有的配置文件之外又加了一份集群配置文件。当集群内节点信息发生变化,如添加节点、节点下线、故障转移等。节点会自动保存集群状态到配置文件中。
  • 需要注意的是,Redis自动维护集群配置文件,不要手动修改,防止节点重启时产生集群信息错乱。
  • 集群配置文件内容记录了集群初始状态,这里最重要的是节点ID,它是一个40位16进制字符串,用于唯一标识集群内一个节点,之后很多集群操作都要借助于节点ID来完成。需要注意是,节点ID不同于运行ID。节点ID在集群初始化时只创建一次,节点重启时会加载集群配置文件进行重用,而Redis的运行ID每次重启都会变化。

7、搭建集群步骤二:节点握手

  • 节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信,达到感知对方的过程。
  • 节点握手是集群彼此通信的第一步,由客户端发起命令:cluster meet {ip} {port}
  • cluster meet命令是一个异步命令,执行之后立刻返回。内部发起与目标节点进行握手通信。
  • cluster meet 127.0.0.1 6380让节点6379和6380节点进行握手通信:
    1. 节点6379本地创建6380节点信息对象,并发送meet消息。
    2. 节点6380接受到meet消息后,保存6379节点信息并回复pong消息。
    3. 之后节点6379和6380彼此定期通过ping/pong消息进行正常的节点通信。
  • 这里的meet、ping、pong消息是Gossip协议通信的载体,它的主要作用是节点彼此交换状态数据信息。
  • 对节点6379和6380分别执行cluster nodes命令,可以看到它们彼此已经感知到对方的存在。
  • 我们只需要在集群内任意节点上执行cluster meet命令加入新节点,握手状态会通过消息在集群内传播,这样其他节点会自动发现新节点并发起握手 流程。
  • 节点建立握手之后集群还不能正常工作,这时集群处于下线状态,所有的数据读写都被禁止。
  • 通过cluster info命令可以获取集群当前状态。
  • 被分配的槽(cluster_slots_assigned)是0 ,由于目前所有的槽没有分配到节点,因此集群无法完成槽到节点的映射。只有当16384个槽全部分配给节点后,集群才进入在线状态。
127.0.0.1:6379> set hello redis
(error) CLUSTERDOWN The cluster is down127.0.0.1:6379> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0

8、搭建集群步骤三:分配槽

// 利用bash特性批量设置槽(slots),命令如下:
redis-cli -h 127.0.0.1  p 6379 cluster addslots {0 . . .5461}
redis-cli -h 127.0.0.1 -p 6380 cluster addslots {5462 . . .10922}
redis-cli -h 127.0.0.1 -p 6381 cluster addslots {10923 . . .16383}// 执行cluster info查看集群状态
127.0.0.1 :6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:0
cluster_stats_messages_sent:4874
cluster_stats_messages_received:4726
  • Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。
  • 通过cluster addslots命令为节点分配槽。
  • 当前集群状态是OK,集群进入在线状态。
  • 所有的槽都已经分配给节点,执行cluster nodes命令可以看到节点和槽的分配关系。
  • 作为一个完整的集群,每个负责处理槽的节点应该具有从节点,保证当它出现故障时可以自动进行故障转移。
  • 集群模式下,Reids节点角色分为主节点和从节点。首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关的数据。
  • 使用cluster replicate {nodeId}命令让一个节点成为从节点。其中命令执行必须在对应的从节点上执行,nodeId是要复制主节点的节点ID。
  • 通过cluster nodes命令查看集群状态和复制关系。
  • Redis官方提供了redis-trib.rb工具方便我们快速搭建集群。

9、用redis-trib.rb搭建集群

redis-trib.rb是采用Ruby实现的Redis集群管理工具。内部通过Cluster相关命令帮我们简化集群创建、检查、槽迁移和均衡等常见运维操作,使用之前需要安装Ruby依赖环境。

1)Ruby环境准备

1、安装Ruby:
-- 下载ruby
wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
-- 安装ruby
tar xvf ruby-2.3.1.tar.gz
./configure -prefix=/usr/local/ruby
make
make install
cd /usr/local/ruby
sudo cp bin/ruby /usr/local/bin
sudo cp bin/gem /usr/local/bin2、安装rubygem redis依赖:
wget http://rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
gem list --check redis gem3、安装redis-trib.rb:
sudo cp /{redis_home}/src/redis-trib.rb /usr/local/bin4、执行redis-trib.rb命令确认环境是否正确:
# redis-trib.rbUsage: redis-trib <command> <options> <arguments . . .>
create          host1:port1 . . . hostN:portN
--replicas <arg>
check           host:port
info            host:port
fix             host:port
--timeout <arg>
reshard         host:port
--from <arg>
--to <arg>
--slots <arg>
--yes
--timeout <arg>
--pipeline <arg>
. . .忽略 . . .
  • 从redis-trib.rb的提示信息可以看出,它提供了集群创建、检查、修复、均衡等命令行工具。
  • 使用redis-trib.rb create命令可快速搭建集群。

2)准备节点

// 准备好节点配置并启动:
redis-server conf/redis-6481.conf
redis-server conf/redis-6482.conf
redis-server conf/redis-6483.conf
redis-server conf/redis-6484.conf
redis-server conf/redis-6485.conf
redis-server conf/redis-6486.conf

3)创建集群

// 使用redis-trib.rb create命令完成节点握手和槽分配过程,命令如下:
redis-trib.rb create --replicas 1 127.0.0.1:6481 127.0.0.1:6482 127.0.0.1:6483 127.0.0.1:6484 127.0.0.1:6485 127.0.0.1:6486
  • –replicas参数指定集群中每个主节点配备几个从节点,这里设置为1。
  • 如果部署节点使用不同的IP地址,redis-trib.rb会尽可能保证主从节点不分配在同一机器下。
  • 节点列表顺序用于确定主从角色,先主节点之后是从节点。
  • 需要注意给redis-trib.rb的节点地址必须是不包含任何槽/数据的节点,否则会拒绝创建集群。

4)集群完整性检查

redis-trib.rb check 127.0.0.1:6481
  • 集群完整性指所有的槽都分配到存活的主节点上,只要16384个槽中有一个没有分配给节点则表示集群不完整。
  • 可以使用redis-trib.rb check命令检测创建的集群是否成功,check命令只需要给出集群中任意一个节点地址就可以完成整个集群的检查工作。

10、节点通信流程

  • 在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态信息。
  • 常见的元数据维护方式分为:集中式和P2P方式。Redis集群采用P2P的Gossip(流言)协议,Gossip协议工作原理就是节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息。
  • 通信过程说明:
    1. 集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000。
    2. 每个节点在固定周期内通过特定规则选择几个节点发送ping消息。
    3. 接收到ping消息的节点用pong消息作为响应。
  • 集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息变更等事件发生时,通过不断的ping/pong消息通信,经过一段时间后所有的节点都会知道整个集群全部节点的最新状态,从而达到集群状态同步的目的。

11、Gossip消息

  • Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息。
  • 常用的Gossip消息可分为: ping消息、pong消息、meet消息、fail消息等。
    1. meet消息:用于通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。
    2. ping消息:集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。ping消息发送封装了自身节点和部分其他节点的状态数据。
    3. pong消息:当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。
    4. fail消息:当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。
  • 所有的消息格式划分为:消息头和消息体。消息头包含发送节点自身状态数据,接收节点根据消息头就可以获取到发送节点的相关数据。
  • 集群内所有的消息都采用相同的消息头结构clusterMsg,它包含了发送节点关键信息,如节点id 、槽映射、节点标识(主从角色,是否下线)等。消息体在Redis内部采用clusterMsgData结构声明。
  • 消息体clusterMsgData定义发送消息的数据,其中ping、meet、pong都采用cluster MsgDataGossip数组作为消息体数据,实际消息类型使用消息头的type属性区分。每个消息体包含该节点的多个clusterMsgDataGossip结构数据,用于信息交换。
  • 当接收到ping、meet消息时,接收节点会解析消息内容并根据自身的识别情况做出相应处理。

12、节点选择

  • Redis集群内节点通信采用固定频率(定时任务每秒执行10次)。因此节点每次选择需要通信的节点列表变得非常重要。通信节点选择过多虽然可以做到信息及时交换但成本过高。节点选择过少会降低集群内所有节点彼此信息交换频率,从而影响故障判定、新节点发现等需求的速度。因此Redis集群的Gossip协议需要兼顾信息交换实时性和成本开销。
  • 消息交换的成本主要体现在单位时间选择发送消息的节点数量和每个消息携带的数据量。

1)选择发送消息的节点数量

  • 集群内每个节点维护定时任务默认每秒执行10次,每秒会随机选取5个节点找出最久没有通信的节点发送ping消息,用于保证Gossip信息交换的随机性。
  • 每100毫秒都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster_node_timeout/2 ,则立刻发送ping消息,防止该节点信息太长时间未更新。
  • 根据以上规则得出每个节点每秒需要发送ping消息的数量= 1+10*num(node.pong_received>cluster_node_timeout/2),因此cluster_node_timeout参数对消息发送的节点数量影响非常大。当我们的带宽资源紧张时,可以适当调大这个参数,如从默认15秒改为30秒来降低带宽占用率。过度调大cluster_node_timeout会影响消息交换的频率从而影响故障转移、槽信息更新、新节点发现的速度。因此需要根据业务容忍度和资源消耗进行平衡。
  • 同时整个集群消息总交换量也跟节点数成正比。

2)消息数据量

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

13、集群伸缩原理

  • Redis集群可以实现对节点的灵活上下线控制。其中原理可抽象为槽和对应数据在不同节点之间灵活移动。
  • 如果希望加入1个节点实现集群扩容时,需要通过相关命令把一部分槽和数据迁移给新节点,因此每个节点负责的槽和数据相比之前变少了,从而达到了集群扩容的目的。

14、扩容集群

1)准备新节点

  • 需要提前准备好新节点并运行在集群模式下,新节点建议跟集群内的节点配置保持一致,便于管理统一。
redis-server conf/redis-6385.conf
redis-server conf/redis-6386.conf

2)加入集群

  • 新节点依然采用cluster meet命令加入到现有集群中。在集群内任意节点执行cluster meet命令让6385和6386节点加入进来。
  • 集群内新旧节点经过一段时间的ping/pong消息通信之后,所有节点会发现新节点并将它们的状态保存到本地。
  • 新节点刚开始都是主节点状态,但是由于没有负责的槽,所以不能接受任何读写操作。对于新节点的后续操作我们一般有两种选择:
    1. 为它迁移槽和数据实现扩容。
    2. 作为其他主节点的从节点负责故障转移。
  • redis-trib.rb工具也实现了为现有集群添加新节点的命令,还实现了直接添加为从节点的支持,命令见下方。内部同样采用cluster meet命令实现加入集群功能。
  • 正式环境建议使用redis-trib.rb add-node命令加入新节点,该命令内部会执行新节点状态检查,如果新节点已经加入其他集群或者包含数据,则放弃集群加入操作并打印相关信息。如果我们手动执行cluster meet命令加入已经存在于其他集群的节点,会造成被加入节点的集群合并到现有集群的情况,从而造成数据丢失和错乱,后果非常严重,线上谨慎操作。
127.0.0.1:6379> cluster meet 127.0.0.1 6385
127.0.0.1:6379> cluster meet 127.0.0.1 6386redis-trib.rb add-node new_host:new_port existing_host:existing_port --slave --master-id <arg>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

3)迁移槽和数据

  • 加入集群后需要为新节点迁移槽和相关数据,槽在迁移过程中集群可以正常提供读写服务,迁移过程是集群扩容最核心的环节。
  • 槽迁移计划:槽是Redis集群管理数据的基本单位,首先需要为新节点制定槽的迁移计划,确定原有节点的哪些槽需要迁移到新节点。迁移计划需要确保每个节点负责相似数量的槽,从而保证各节点的数据均匀。
  • 迁移数据:数据迁移过程是逐个槽进行的,每个槽数据迁移的流程说明如下:
    1. 对目标节点发送cluster setslot {slot} importing {sourceNodeId}命令,让目标节点准备导入槽的数据。
    2. 对源节点发送cluster setslot {slot} migrating {targetNodeId}命令,让源节点准备迁出槽的数据。
    3. 源节点循环执行cluster getkeysinslot {slot} {count}命令,获取count个属于槽{slot}的键。
    4. 在源节点上执行migrate {targetIp}{targetPort}""0{timeout}keys {keys…}命令,把获取的键通过流水线(pipeline)机制批量迁移到目标节点,批量迁移版本的migrate命令在Redis3.0.6以上版本提供,之前的migrate命令只能单个键迁移。对于大量key的场景,批量键迁移将极大降低节点之间网络IO次数。
    5. 重复执行步骤3)和步骤4)直到槽下所有的键值数据迁移到目标节点。
    6. 向集群内所有主节点发送cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点。为了保证槽节点映射变更及时传播,需要遍历发送给所有主节点更新被迁移的槽指向新节点。
  • 添加从节点:使用cluster replicate {masterNodeId}命令为主节点添加对应从节点,注意在集群模式下slaveof添加从节点操作不再支持。从节点内部除了对主节点发起全量复制之外,还需要更新本地节点的集群相关状态。
根据以上迁移数据流程,手动使用命令把源节点6379负责的槽4096迁移到目标节点6385中,流程如下:
1、目标节点准备导入槽4096数据:
127.0.0.1:6385> cluster setslot 4096 importing cfb28ef1deee4e0fa78da86abe5d24566
OK确认槽4096导入状态开启:
127.0.0.1:6385> cluster nodes
1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 127.0.0.1:6385 myself,master - 0 0 7 co [4096 < cfb28ef1deee4e0fa78da86abe5d24566744411e]2、源节点准备导出槽4096数据:
127.0.0.1:6379>cluster setslot 4096 migrating 1a205dd8b2819a00dd1e8b6be40a8e2abe
OK确认槽4096导出状态开启:
127.0.0.1:6379> cluster nodes
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 co 0-5461 [4096->-1a205dd8b2819a00dd1e8b6be40a8e2abe77b756]3、批量获取槽4096对应的键,这里我们获取到3个处于该槽的键:
127.0.0.1:6379> cluster getkeysinslot 4096 100
1) "key:test:5028"
2) "key:test:68253"
3) "key:test:79212"确认这三个键是否存在于源节点:
127.0.0.1:6379>mget key:test:5028 key:test:68253 key:test:79212
1) "value:5028"
2) "value:68253"
3) "value:79212"批量迁移这3个键,migrate命令保证了每个键迁移过程的原子性:
127.0.0.1:6379> migrate 127.0.0.1 6385 "" 0 5000 keys key:test:5028 key:test:682 key:test:79212继续查询这三个键,发现已经不在源节点中,Redis返回ASK转向错误,ASK转向负责引导客户端找到数据所在的节点:
127.0.0.1:6379> mget key:test:5028 key:test:68253 key:test:79212
(error) ASK 4096 127.0 .0.1:6385通知所有主节点槽4096指派给目标节点6385:
127.0.0.1:6379> cluster setslot 4096 1a205dd8b2819a00dd1e8b6be40a8e2abe77b7
127.0.0.1:6380> cluster setslot 4096 1a205dd8b2819a00dd1e8b6be40a8e2abe77b7
127.0.0.1:6381> cluster setslot 4096 1a205dd8b2819a00dd1e8b6be40a8e2abe77b7
127.0.0.1:6385> cluster setslot 4096 1a205dd8b2819a00dd1e8b6be40a8e2abe77b7 确认源节点6379不再负责槽4096改为目标节点6385负责:
127.0.0.1:6379> cluster nodes
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 co
0-4095 4097-5461
1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 127.0.0.1:6385 master - 0 146971801107
connected 4096

实际操作槽迁移过程时肯定涉及大量槽并且每个槽对应非常多的键。因此redis-trib提供了槽重分片功能,命令如下:
redis-trib.rb reshard host:port --from <arg> --to <arg> --slots <arg> --yes --t <arg> --pipeline <arg>
参数说明:

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

reshard命令简化了数据迁移的工作量,其内部针对每个槽的数据迁移同样使用之前的流程。

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

15、收缩集群

安全下线节点的流程说明:

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

1)下线迁移槽

  • 下线节点需要把自己负责的槽迁移到其他节点,原理与之前节点扩容的迁移槽过程一致。
  • 收缩正好和扩容迁移方向相反。
  • 下线节点槽迁出完成后,剩下的步骤需要让集群忘记该节点。

2)忘记节点

  • 由于集群内的节点不停地通过Gossip消息彼此交换节点状态,因此需要通过一种健壮的机制让集群内所有节点忘记下线的节点。也就是说让其他节点不再与要下线节点进行Gossip消息交换。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}命令。
  • 当下线主节点具有从节点时需要把该从节点指向到其他主节点,因此对于主从节点都下线的情况,建议先下线从节点再下线主节点,防止不必要的全量复制。

16、请求路由——请求重定向

  • Redis集群对客户端通信协议做了比较大的修改,为了追求性能最大化,并没有采用代理的方式而是采用客户端直连节点的方式。因此对于希望从单机切换到集群环境的应用需要修改客户端代码。
  • 在集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重定向。
  • 可以借助cluster keyslot{key}命令返回key所对应的槽。
  • 重定向信息包含了键所对应的槽以及负责该槽的节点地址,根据这些信息客户端就可以向正确的节点发起请求。
  • 使用redis-cli命令时,可以加入-c参数支持自动重定向,简化手动发起重定向操作。
  • redis-cli自动帮我们连接到正确的节点执行命令,这个过程是在redis-cli内部维护,实质上是client端接到MOVED信息之后再次发起请求,并不在Redis节点中完成请求转发。
  • 节点对于不属于它的键命令只回复重定向响应,并不负责转发。
  • 正因为集群模式下把解析发起重定向的过程放到客户端完成,所以集群客户端协议相对于单机有了很大的变化。
  • 键命令执行步骤主要分两步:计算槽,查找槽所对应的节点。
127.0.0.1:6379> cluster keyslot key:test:1
(integer) 5191
127.0.0.1:6379> cluster nodes
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master
1366-4095 4097-5461 12288-13652127.0.0.1:6379> set key:test:2 value-2
(error) MOVED 9252 127.0.0.1:6380
127.0.0.1:6379> cluster keyslot key:test:2
(integer) 9252

1)计算槽

  • Redis首先需要计算键所对应的槽。根据键的有效部分使用CRC16函数计算出散列值,再取对16383的余数,使每个键都可以映射到0~16383槽范围内。
  • 如果键内容包含{和}大括号字符,则计算槽的有效部分是括号内的内容;否则采用键的全内容计算槽。其中键内部使用大括号包含的内容又叫做hash_tag,它提供不同的键可以具备相同slot的功能,常用于Redis IO优化。例如在集群模式下使用mget等命令优化批量调用时,键列表必须具有相同的slot,否则会报错。这时可以利用hash_tag让不同的键具有相同的slot达到优化的目的。
  • Pipeline同样可以受益于hash_tag,由于Pipeline只能向一个节点批量发送执行命令,而相同slot必然会对应到唯一的节点,降低了集群使用Pipeline的门槛。

2)槽节点查找

  • Redis计算得到键对应的槽后,需要查找槽所对应的节点。集群内通过消息交换每个节点都会知道所有节点的槽信息,内部保存在clusterState结构中。
  • 根据MOVED重定向机制,客户端可以随机连接集群内任一Redis获取键所在节点,这种客户端又叫Dummy(傀儡)客户端,它优点是代码实现简单,对客户端协议影响较小,只需要根据重定向信息再次发送请求即可。但是它的弊端很明显,每次执行键命令前都要到Redis上进行重定向才能找到要执行命令的节点,额外增加了IO开销,这不是Redis集群高效的使用方式。正因为如此通常集群客户端都采用另一种实现:Smart(智能)客户端。

17、请求路由——Smart客户端

1)Smart客户端原理

  • 大多数开发语言的Redis客户端都采用Smart客户端支持集群协议。
  • Smart客户端通过在内部维护slot→node的映射关系,本地就可实现键到节点的查找,从而保证IO效率的最大化,而MOVED重定向负责协助Smart客户端更新slot→node映射。
  • Smart客户端操作集群的流程:略。

2)Smart客户端——JedisCluster

  • Redis Cluster虽然提供了分布式的特性,但是有些命令或者操作,诸如keys、flushall、删除指定模式的键,需要遍历所有节点才可以完成。
  • Redis Cluster中,由于key分布到各个节点上,会造成无法实现mget、mset等功能。但是可以利用CRC16算法计算出key对应的slot,以及Smart客户端保存了slot和节点对应关系的特性,将属于同一个Redis节点的key进行归档,然后分别对每个节点对应的子key列表执行mget或者pipeline操作。
  • Lua和事务需要所操作的key,必须在一个节点上,不过Redis Cluster提供了hashtag,如果开发人员确实要使用Lua或者事务,可以将所要操作的key使用一个hashtag。

18、请求路由——ASK重定向

1)客户端ASK重定向流程

  • Redis集群支持在线迁移槽(slot)和数据来完成水平伸缩,当slot对应的数据从源节点到目标节点迁移过程中,客户端需要做到智能识别,保证键命令可正常执行。
  • 当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分数据在源节点,而另一部分在目标节点的情况时,客户端键命令执行流程将发生变化:
    1. 客户端根据本地slots缓存发送命令到源节点,如果存在键对象则直接执行并返回结果给客户端。
    2. 如果键对象不存在,则可能存在于目标节点,这时源节点会回复ASK重定向异常。格式如下:(error)ASK{slot}{targetIP} :{targetPort}。
    3. 客户端从ASK重定向异常提取出目标节点信息,发送asking命令到目标节点打开客户端连接标识,再执行键命令。如果存在则执行,不存在则返回不存在信息。
  • ASK与MOVED虽然都是对客户端的重定向控制,但是有着本质区别。ASK重定向说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只能是临时性的重定向,客户端不会更新slots缓存。但是MOVED重定向说明键对应的槽已经明确指定到新的节点,因此需要更新slots缓存。

2)节点内部处理

  • 为了支持ASK重定向,源节点和目标节点在内部的clusterState结构中维护当前正在迁移的槽信息,用于识别槽迁移情况。
  • 节点每次接收到键命令时,都会根据clusterState内的迁移属性进行命令处理,如下所示:
    1. 如果键所在的槽由当前节点负责,但键不存在则查找migrating_slots_to数组查看槽是否正在迁出,如果是返回ASK重定向。
    2. 如果客户端发送asking命令打开了CLIENT_ASKING标识,则该客户端下次发送键命令时查找importing_slots_from数组获取clusterNode ,如果指向自身则执行命令。
    3. 需要注意的是,asking命令是一次性命令,每次执行完后客户端标识都会修改回原状态,因此每次客户端接收到ASK重定向后都需要发送asking命令。
    4. 批量操作。ASK重定向对单键命令支持得很完善,但是,在开发中我们经常使用批量操作,如mget或pipeline。当槽处于迁移状态时,批量操作会受到影响。
  • 当在集群环境下使用mget、mset等批量操作时,slot迁移数据期间由于键列表无法保证在同一节点,会导致大量错误。
  • 使用smart客户端批量操作集群时,需要评估mget/mset、Pipeline等方式在slot迁移场景下的容错性,防止集群迁移造成大量错误和数据丢失的情况。
  • 集群环境下对于使用批量操作的场景,建议优先使用Pipeline方式,在客户端实现对ASK重定向的正确处理,这样既可以受益于批量操作的IO优化,又可以兼容slot迁移场景。

19、故障转移之故障发现

  • Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)。

    1. 主观下线:指某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
    2. 客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。
  • 主观下线:集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果在cluster-node-timeout时间内通信一直失败,则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。流程说明如下:
    1. 节点a发送ping消息给节点b,如果通信正常将接收到pong消息,节点a更新最近一次与节点b的通信时间。
    2. 如果节点a与节点b通信出现问题则断开连接,下次会进行重连。如果一直通信失败,则节点a记录的与节点b最后通信时间将无法更新。
    3. 节点a内的定时任务检测到与节点b最后通信时间超高cluster-node-timeout时,更新本地对节点b的状态为主观下线(pfail)。
  • 主观下线简单来讲就是,当cluster-note-timeout时间内某节点无法与另一个节点顺利完成ping消息通信时,则将该节点标记为主观下线状态。每个节点内的cluster State结构都需要保存其他节点信息,用于从自身视角判断其他节点的状态。结构关键属性中最重要的属性是flags,用于标示该节点对应状态。
  • 当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。ping/pong消息的消息体会携带集群1/10的其他节点状态数据,当接受节点发现消息体中含有主观下线的节点状态时,会在本地找到故障节点的ClusterNode结构,保存到下线报告链表中。通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时,触发客观下线流程。流程说明如下:
    1. 当消息体内含有其他节点的pfail状态会判断发送节点的状态,如果发送节点是主节点则对报告的pfail状态处理,从节点则忽略。
    2. 找到pfail对应的节点结构,更新clusterNode内部下线报告链表。
    3. 根据更新后的下线报告链表告尝试进行客观下线。
  • 为什么必须是负责槽的主节点参与故障发现决策?因为集群模式下只有处理槽的主节点才负责读写请求和集群槽等关键信息维护,而从节点只进行主节点数据和状态信息的复制。
  • 为什么半数以上处理槽的主节点?必须半数以上是为了应对网络分区等原因造成的集群分割情况,被分割的小集群因为无法完成从主观下线到客观下线这一关键过程,从而防止小集群完成故障转移之后继续对外提供服务。
  • 每个节点ClusterNode结构中都会存在一个下线链表结构,保存了其他主节点针对当前节点的下线报告。下线报告中保存了报告故障的节点结构和最近收到下线报告的时间,当接收到fail状态时,会维护对应节点的下线上报链表。每个下线报告都存在有效期,每次在尝试触发客观下线时,都会检测下线报告是否过期,对于过期的下线报告将被删除。如果在cluster-node-time2的时间内该下线报告没有得到更新则过期并删除。下线报告的有效期限是server.cluster_node_timeout2 ,主要是针对故障误报的情况。
  • 如果在cluster-node-time*2时间内无法收集到一半以上槽节点的下线报告,那么之前的下线报告将会过期,也就是说主观下线上报的速度追赶不上下线报告过期的速度,那么故障节点将永远无法被标记为客观下线从而导致故障转移失败。因此不建议将cluster-node-time设置得过小。
  • 集群中的节点每次接收到其他节点的pfail状态,都会尝试触发客观下线,流程说明如下:
    1. 首先统计有效的下线报告数量,如果小于集群内持有槽的主节点总数的一半则退出。
    2. 当下线报告大于槽主节点数量一半时,标记对应故障节点为客观下线状态。
    3. 向集群广播一条fail消息,通知所有的节点将故障节点标记为客观下线,fail消息的消息体只包含故障节点的ID。
  • 广播fail消息是客观下线的最后一步,它承担着非常重要的职责:
    1. 通知集群内所有的节点标记故障节点为客观下线状态并立刻生效。
    2. 通知故障节点的从节点触发故障转移流程。
  • 网络分区会导致分割后的小集群无法收到大集群的fail消息,因此如果故障节点所有的从节点都在小集群内将导致无法完成后续故障转移,因此部署主从结构时需要根据自身机房/机架拓扑结构,降低主从被分区的可能性。

20、故障转移之故障恢复

  • 故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的所有从 节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程。流程说明如下:

    1. 资格检查:每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障的主节点。如果从节点与主节点断线时间超过cluster-node-time*cluster-slave-validity-factor,则当前从节点不具备故障转移资格。参数cluster-slave-validity-factor用于从节点的有效因子,默认为10。
    2. 准备选举时间:当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该时间后才能执行后续流程。之所以采用延迟触发机制,主要是通过对多个从节点使用不同的延迟选举时间来支持优先级问题。复制偏移量越大说明从节点延迟越低,那么它应该具有更高的优先级来替换故障主节点。所有的从节点中复制偏移量最大的将提前触发故障选举流程。
    3. 发起选举:当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程如下:
      1. 更新配置纪元:配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元(clusterNode.configEpoch)标示当前主节点的版本,所有主节点的配置纪元都不相等,从节点会复制主节点的配置纪元。整个集群又维护一个全局的配置纪元(clusterState.current Epoch),用于记录集群内所有主节点配置纪元的最大版本。
      2. 广播选举消息:在集群内广播选举消息(FAILOVER_AUTH_REQUEST),并记录已发送过消息的状态,保证该从节点在一个配置纪元内只能发起一次选举。消息内容如同ping消息只是将type类型变为FAILOVER_AUTH_REQUEST。
    4. 选举投票:只有持有槽的主节点才会处理故障选举消息(FAILOVER_AUTH_REQUEST),因为每个持有槽的节点在一个配置纪元内都有唯一的一张选票,当接到第一个请求投票的从节点消息时回复FAILOVER_AUTH_ACK消息作为投票,之后相同配置纪元内其他从节点的选举消息将忽略。
    5. 替换主节点:当从节点收集到足够的选票之后,触发替换主节点操作:
      1. 当前从节点取消复制变为主节点。
      2. 执行clusterDelSlot操作撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽委派给自己。
      3. 向集群广播自己的pong消息,通知集群内所有的节点当前从节点变为主节点并接管了故障主节点的槽信息。
  • 执行cluster info命令可以查看配置纪元信息。
  • 配置纪元会跟随ping/pong消息在集群内传播,当发送方与接收方都是主节点且配置纪元相等时代表出现了冲突,nodeId更大的一方会递增全局配置纪元并赋值给当前节点来区分冲突。
  • 配置纪元的主要作用:
    1. 标示集群内每个主节点的不同版本和当前集群最大的版本。
    2. 每次集群发生重要事件时,这里的重要事件指出现新的主节点(新加入的或者由从节点转换而来),从节点竞争选举。都会递增集群全局的配置纪元并赋值给相关主节点,用于记录这一关键事件。
    3. 主节点具有更大的配置纪元代表了更新的集群状态,因此当节点间进行ping/pong消息交换时,如出现slots等关键信息不一致时,以配置纪元更大的一方为准,防止过时的消息状态污染集群。
  • 配置纪元的应用场景有:
    1. 新节点加入。
    2. 槽节点映射冲突检测。
    3. 从节点投票选举冲突检测。
  • 之前在通过cluster setslot命令修改槽节点映射时,需要确保执行请求的主节点本地配置纪元(configEpoch)是最大值,否则修改后的槽信息在消息传播中不会被拥有更高的配置纪元的节点采纳。由于Gossip通信机制无法准确知道当前最大的配置纪元在哪个节点,因此在槽迁移任务最后的cluster setslot {slot} node {nodeId}命令需要在全部主节点中执行一遍。
  • 从节点每次发起投票时都会自增集群的全局配置纪元,并单独保存在clusterState.failover_auth_epoch变量中用于标识本次从节点发起选举的版本。
  • 投票过程其实是一个领导者选举的过程,如集群内有N个持有槽的主节点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给一个从节点,因此只能有一个从节点获得N/2+ 1的选票,保证能够找出唯一的从节点。
  • Redis集群没有直接使用从节点进行领导者选举,主要因为从节点数必须大于等于3个才能保证凑够N/2+1个节点,将导致从节点资源浪费。使用集群内所有持有槽的主节点进行领导者选举,即使只有一个从节点也可以完成选举过程。当从节点收集到N/2+ 1个持有槽的主节点投票时,从节点可以执行替换主节点操作。
  • 故障主节点也算在投票数内,假设集群内节点规模是3主3从,其中有2个主节点部署在一台机器上,当这台机器宕机时,由于从节点无法收集到3/2+1个主节点选票将导致故障转移失败。这个问题也适用于故障发现环节。因此部署集群时所有主节点最少需要部署在3台物理机上才能避免单点问题。
  • 投票作废:每个配置纪元代表了一次选举周期,如果在开始投票之后的cluster-node-timeout*2时间内从节点没有获取足够数量的投票,则本次选举作废。从节点对配置纪元自增并发起下一轮投票,直到选举成功为止。

21、故障转移之故障转移时间

  • 估算出故障转移时间:

    1. 主观下线(pfail)识别时间=cluster-node-timeout。
    2. 主观下线状态消息传播时间<=cluster-node-timeout/2。消息通信机制对超过cluster-node-timeout/2未通信节点会发起ping消息,消息体在选择包含哪些节点时会优先选取下线状态节点,所以通常这段时间内能够收集到半数以上主节点的pfail报告从而完成故障发现。
    3. 从节点转移时间<=1000毫秒。由于存在延迟发起选举机制,偏移量最大的从节点会最多延迟1秒发起选举。通常第一次选举就会成功,所以从节点执行转移时间在1秒以内。
      根据以上分析可以预估出故障转移时间,如下:
      failover-time (毫秒) ≤ cluster-node-timeout + cluster-node-timeout/2 + 1000
      因此,故障转移时间跟cluster-node-timeout参数息息相关,默认15秒。配置时可以根据业务容忍度做出适当调整,但不是越小越好。

22、集群运维——集群完整性

  • 为了保证集群完整性,默认情况下当集群16384个槽任何一个没有指派到节点时整个集群不可用。执行任何键命令返回(error)CLUSTERDOWN Hash slot not served错误。
  • 当持有槽的主节点下线时,从故障发现到自动完成转移期间整个集群是不可用状态,对于大多数业务无法容忍这种情况, 因此建议将参数cluster-require-full-coverage配置为no,当主节点故障时只影响它负责槽的相关命令执行,不会影响其他主节点的可用性。

23、集群运维——带宽消耗

  • 集群内Gossip消息通信本身会消耗带宽,官方建议集群最大规模在1000以内,也是出于对消息通信成本的考虑,因此单集群不适合部署超大规模的节点。
  • 节点间消息通信对带宽的消耗体现在以下几个方面:
    1. 消息发送频率:跟cluster-node-timeout密切相关,当节点发现与其他节点最后通信时间超过cluster-node-timeout/2时会直接发送ping消息。
    2. 消息数据量:每个消息主要的数据占用包含:slots槽数组(2KB空间)和整个集群1/10的状态数据(10个节点状态数据约1KB)。
    3. 节点部署的机器规模:机器带宽的上线是固定的,因此相同规模的集群分布的机器越多每台机器划分的节点越均匀,则集群内整体的可用带宽越高。
  • 集群带宽消耗主要分为:读写命令消耗+Gossip消息消耗。因此搭建Redis集群时需要根据业务数据规模和消息通信成本做出合理规划:
    1. 在满足业务需要的情况下尽量避免大集群。同一个系统可以针对不同业务场景拆分使用多套集群。这样每个集群既满足伸缩性和故障转移要求,还可以规避大规模集群的弊端。
    2. 适度提高cluster-node-timeout降低消息发送频率,同时cluster-node-timeout还影响故障转移的速度,因此需要根据自身业务场景兼顾二者的平衡。
    3. 如果条件允许集群尽量均匀部署在更多机器上。避免集中部署,如集群有60个节点,集中部署在3台机器上每台部署20个节点,这时机器带宽消耗将非常严重。

24、集群运维——Pub/Sub广播问题

  • Redis在2.0版本提供了Pub/Sub(发布/订阅)功能,用于针对频道实现消息的发布和订阅。但是在集群模式下内部实现对所有的publish命令都会向所有的节点进行广播,造成每条publish数据都会在集群内所有节点传播一次,加重带宽负担。
  • 当频繁应用 Pub/Sub功能时应该避免在大量节点的集群内使用,否则会严重消耗集群内网络带宽。针对这种情况建议使用sentinel结构专门用于Pub/Sub功能,从而规避这一问题。

25、集群运维——集群倾斜

  • 集群倾斜指不同节点之间数据量和请求量出现明显差异。

1)数据倾斜

  • 数据倾斜主要分为以下几种:

    1. 节点和槽分配严重不均。

      • 针对每个节点分配的槽不均的情况,可以使用redis-trib.rb info {host:ip}进行定位,会列举出每个节点负责的槽和键总量以及每个槽平均键数量。
      • 当节点对应槽数量不均匀时,可以使用redis-trib.rb rebalance命令进行平衡。
    2. 不同槽对应键数量差异过大。
      • 键通过CRC16哈希函数映射到槽上,正常情况下槽内键数量会相对均匀。但当大量使用hash_tag时,会产生不同 的键映射到同一个槽的情况。特别是选择作为hash_tag的数据离散度较差时,将加速槽内键数量倾斜情况。
      • 通过命令:cluster countkeysinslot{slot}可以获取槽对应的键数量,识别出哪些槽映射了过多的键。再通过命令cluster getkeysinslot{slot}{count}循环迭代出槽下所有的键。从而发现过度使用hash_tag的键。
    3. 集合对象包含大量元素。
      • 对于大集合对象的识别可以使用redis-cli-- bigkeys命令识别。找出大集合之后可以根据业务场景进行拆分。
      • 同时集群槽数据迁移是对键执行migrate操作完成,过大的键集合如几百兆,容易造成migrate命令超时导致数据迁移失败。
    4. 内存相关配置不一致。
      • 内存相关配置指hash-max-ziplist-value、set-max-intset-entries等压缩数据结构配置。当集群大量使用hash、set等数据结构时,如果内存压缩数据结构配置不一致,极端情况下会相差数倍的内存,从而造成节点内存量倾斜。

2)请求倾斜

  • 集群内特定节点请求量/流量过大将导致节点之间负载不均,影响集群均衡和运维成本。常出现在热点键场景,当键命令消耗较低时如小对象的get、set、incr等,即使请求量差异较大一般也不会产生负载严重不均。
  • 但是当热点键对应高算法复杂度的命令或者是大对象操作如hgetall、smembers等,会导致对应节点负载过高的情况。避免方式如下:
    1. 合理设计键,热点大集合对象做拆分或使用hmget替代hgetall避免整体读取。
    2. 不要使用热键作为hash_tag,避免映射到同一槽。
    3. 对于一致性要求不高的场景,客户端可使用本地缓存减少热键调用。

26、集群运维——集群读写分离

1)只读连接

  • 集群模式下从节点不接受任何读写请求,发送过来的键命令会重定向到负责槽的主节点上(其中包括它的主节点)。
  • 当需要使用从节点分担主节点读压力时,可以使用readonly命令打开客户端连接只读状态。之前的复制配置slave-read-only在集群模式下无效。当开启只读状态时,从节点接收读命令处理流程变为:如果对应的槽属于自己正在复制的主节点则直接执行读命令,否则返回重定向信息。
  • readonly命令是连接级别生效,因此每次新建连接时都需要执行readonly开启只读状态。执行readwrite命令可以关闭连接只读状态。

2)读写分离

  • 针对从节点故障问题,客户端需要维护可用节点列表,集群提供了cluster slaves {nodeId}命令,返回nodeId对应主节点下所有从节点信息,数据格式同cluster nodes。解析从节点列表信息,排除fail状态节点,这样客户端对从节点的故障判定可以委托给集群处理,简化维护可用从节点列表难度。
  • 集群模式下读写分离涉及对客户端修改如下:
    1. 维护每个主节点可用从节点列表。
    2. 针对读命令维护请求节点路由。
    3. 从节点新建连接开启readonly状态。
  • 集群模式下读写分离成本比较高,可以直接扩展主节点数量提高集群性能,一般不建议集群模式下做读写分离。
  • 集群读写分离有时用于特殊业务场景如:
    1. 利用复制的最终一致性使用多个从节点做跨机房部署降低读命令网络延迟。
    2. 主节点故障转移时间过长,业务端把读请求路由给从节点保证读操作可用。
    3. 以上场景也可以在不同机房独立部署Redis集群解决,通过客户端多写来维护,读命令直接请求到最近机房的Redis集群,或者当一个集群节点故障时客户端转向另一个集群。

27、集群运维——手动故障转移

  • Redis集群提供了手动故障转移功能:指定从节点发起转移流程,主从节点角色进行切换,从节点变为新的主节点对外提供服务,旧的主节点变为它的从节点。
  • 在从节点上执行cluster failover命令发起转移流程,默认情况下转移期间客户端请求会有短暂的阻塞,但不会丢失数据,流程如下:
    1. 从节点通知主节点停止处理所有客户端请求。
    2. 主节点发送对应从节点延迟复制的数据。
    3. 从节点接收处理复制延迟的数据,直到主从复制偏移量一致为止,保证复制数据不丢失。
    4. 从节点立刻发起投票选举(这里不需要延迟触发选举)。选举成功后断开复制变为新的主节点,之后向集群广播主节点pong消息。
    5. 旧主节点接受到消息后更新自身配置变为从节点,解除所有客户端请求阻塞,这些请求会被重定向到新主节点上执行。
    6. 旧主节点变为从节点后,向新的主节点发起全量复制流程。
  • 主从节点转移后,新的从节点由于之前没有缓存主节点信息无法使用部分复制功能,所以会发起全量复制,当节点包含大量数据时会严重消耗CPU和网络资源,线上不要频繁操作。Redis4.0的Psync2将有效改善这一问题。
  • 手动故障转移的应用场景主要如下:
    1. 主节点迁移:运维Redis集群过程中经常遇到调整节点部署的问题,如节点所在的老机器替换到新机器等。由于从节点默认不响应请求可以安全下线关闭,但直接下线主节点会导致故障自动转移期间主节点无法对外提供服务,影响线上业务的稳定性。这时可以使用手动故障转移,把要下线的主节点安全的替换为从节点后,再做下线操作操作。
    2. 强制故障转移。当自动故障转移失败时,只要故障的主节点有存活的从节点就可以通过手动转移故障强制让从节点替换故障的主节点,保证集群的可用性。
    3. 自动故障转移失败的场景有:
      1. 主节点和它的所有从节点同时故障。这个问题需要通过调整节点机器部署拓扑做规避,保证主从节点不在同一机器/机架上。除非机房内大面积故障,否则两台机器/机架同时故障概率很低。
      2. 所有从节点与主节点复制断线时间超过cluster-slave-validity-factor*cluster-node-tineout+repl-ping-slave-period,导致从节点被判定为没有故障转移资格,手动故障转移从节点不做中断超时检查。
      3. 由于网络不稳定等问题,故障发现或故障选举时间无法在cluster-node-timeout*2内完成,流程会不断重试,最终从节点复制中断时间超时,失去故障转移资格无法完成转移。
      4. 集群内超过一半以上的主节点同时故障。
  • 根据以上情况,cluster failover命令提供了两个参数force/takeover提供支持:、
    1. cluster failover force——用于当主节点宕机且无法自动完成故障转移情况。从节点接到cluster failover force请求时,从节点直接发起选举,不再跟主节点确认复制偏移量(从节点复制延迟的数据会丢失),当从节点选举成功后替换为新的主节点并广播集群配置。
    2. cluster failover takeover——用于集群内超过一半以上主节点故障的场景,因为从节点无法收到半数以上主节点投票,所以无法完成选举过程。可以执行cluster failover takeover强制转移,接到命令的从节点不再进行选举流程而是直接更新本地配置纪元并替换主节点。takeover故障转移由于没有通过领导者选举发起故障转移,会导致配置纪元存在冲突的可能。当冲突发生时,集群会以nodeId字典序更大的一方配置为准。因此要小心集群分区后,手动执行takeover导致的集群冲突问题。
  • 在集群可以自动完成故障转移的情况下,不要使用cluster failover takeover强制干扰集群选举机制,该操作主要用于半数以上主节点故障时采取的强制措施,请慎用。
  • 手动故障转移时,在满足当前需求的情况下建议优先级:cluster failver>cluster failover force>cluster failover takeover。

28、集群运维——数据迁移

  • 应用Redis集群时,常需要把单机Redis数据迁移到集群环境。redis-trib.rb工具提供了导入功能,用于数据从单机向集群环境迁移的场景。
    redis-trib .rb import host :port --from <arg> --copy --replace
  • redis-trib.rb import命令内部采用批量scan和migrate 的方式迁移数据。这种迁移方式存在以下缺点:
    1. 迁移只能从单机节点向集群环境导入数据。
    2. 不支持在线迁移数据,迁移数据时应用方必须停写,无法平滑迁移数据。
    3. 迁移过程中途如果出现超时等错误,不支持断点续传只能重新全量导入。
    4. 使用单线程进行数据迁移,大数据量迁移速度过慢。
  • 唯品会开发的redis-migrate-tool(https://github.com/vipshop/redis-migrate-tool),该工具可满足大多数Redis迁移需求,特点如下:
    1. 支持单机、Twemproxy、Redis Cluster 、RDB/AOF等多种类型的数据迁移。
    2. 工具模拟成从节点基于复制流迁移数据,从而支持在线迁移数据,业务方不需要停写。
    3. 采用多线程加速数据迁移过程且提供数据校验和查看迁移状态等功能。

【Redis】《Redis 开发与运维》笔记-Chapter10-集群相关推荐

  1. 【运维】K8S集群部署系列之ETCD集群搭建(四)

    ETCD集群扩容和缩容 本文将介绍生产环境下如何对ETCD集群进行扩容和缩容. 文章目录 ETCD集群扩容和缩容 新节点环境准备(node3) 下载安装包并初始化环境 网络准备 生成`node3`对等 ...

  2. Redis开发与运维笔记_1 初识2 API

    目录 概述 第1章 初识Redis 1.1 盛赞Redis 1.2 Redis特性 1.3 配置.启动.操作.关闭Redis 第2章 API的理解和使用 2.1 预备 2.1.1 全局命令 2.1.2 ...

  3. 跟老男孩学 Linux 运维:Web 集群实战

    内容简介 本书针对中小规模网站集群的搭建.部署.优化进行了详细讲解,全书可分为三大部分,其中第一部分讲的是 Linux 相关的基础且重要的知识,第二部分针对当下流行的 Web 环境架构(LNMP)的搭 ...

  4. 运维利器-ClusterShell集群管理操作记录

    在运维实战中,如果有若干台数据库服务器,想对这些服务器进行同等动作,比如查看它们当前的即时负载情况,查看它们的主机名,分发文件等等,这个时候该怎么办?一个个登陆服务器去操作,太傻帽了!写个shell去 ...

  5. 运维必备_ganglia集群监控服务端及客户端配置实战

    0x01.前言 Ganglia是UC Berkeley发起的一个开源集群监视项目,设计用于测量数以千计的节点.Ganglia的核心包含gmond.gmetad以及一个Web前端.主要是用来监控系统性能 ...

  6. 运维必备:ganglia集群监控服务端及客户端配置实战

    0x01.前言 Ganglia是UC Berkeley发起的一个开源集群监视项目,设计用于测量数以千计的节点.Ganglia的核心包含gmond.gmetad以及一个Web前端.主要是用来监控系统性能 ...

  7. 写给java开发的运维笔记

    转载至:https://github.com/biezhi/java-bible/tree/master/learn_server 转载于:https://www.cnblogs.com/zhuawa ...

  8. 解密:IT运维艺术之集群(4层AND7层)

    负载均衡 概述; 负载均衡 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性. 负载均衡,英文名称为Load ...

  9. Linux系统运维之Zookeeper集群配置

    一.简介 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.ZooKeeper的目标就是封装好复杂易 ...

  10. 4.2.9 Kafka集群与运维, 应用场景, 集群搭建, 集群监控JMX(度量指标, JConsole, 编程获取, Kafka Eagle)

    目录 3.1 集群应用场景 1 消息传递 2 网站活动路由 3 监控指标 4 日志汇总 5 流处理 6 活动采集 7 提交日志 总结 3.2 集群搭建 3.2.1 Zookeeper集群搭建 3.2. ...

最新文章

  1. Python培训分享:PyQT是什么?PyQt4和PyQt5的区别是什么?
  2. 刚刚!饶毅再次撰文,回应耿美玉,称其未遵守学术规范
  3. 成功解决win系统电脑中网络仅看到自己计算机图文教程
  4. php 导入表格数据,PHPExcel 导入Excel数据的方法
  5. 【目标检测_keypoint based 方法系列】基于关键点的目标检测
  6. asp登录页面跳转到注册页面_Java 添加页面跳转按钮到PDF文档
  7. 第5课 开心的金明《聪明人的游戏:信息学探秘.提高篇》(优化空间)
  8. 长城 宽带 +招商银行专业版
  9. Logback配置文件这么写,TPS提高10倍!
  10. java 函数式编程应用_java8 函数式编程应用
  11. 【Java web 项目】医院药品管理系统
  12. JAVASE,JAVAEE,JAVAME的区别
  13. 网页完整的长截图怎么截?3步搞定!
  14. vim生成连续数字列
  15. 8253(8254)计时/计数器简介
  16. 5、隐私计算--同态加密
  17. [并发并行]_[线程模型]_[Pthread线程使用模型之一管道Pipeline]
  18. WIN10笔记本屏幕亮度无法调节,一直是最高亮度
  19. 获取XBOX360体感大冒险照片
  20. web服务器之Ser-U和NFS

热门文章

  1. 计算机电脑连接wifi,计算机应如何连接到WiFi?笔记本WiFi设置方法[详细步骤]
  2. java.lang.IllegalStateException崩溃处理——4种情形
  3. 常用的4种黑盒测试方法
  4. Centos7部署开源聊天软件rocket.chat
  5. 泛微协同办公平台E-cology9.0版本最全帮助文档
  6. 视觉SLAM十四讲第三讲
  7. 大数据案例分析|谁在愚人节最容易被骗?
  8. matlab函数建模例题,数学建模matlab上机实训题目答案
  9. cmd指令-快捷查看电脑ip地址
  10. java课程设计员工信息管理系统,javaweb课程设计之员工信息管理系统