redis学习笔记10-集群

当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构方案达到负载均衡的目的。Redis Cluster是Redis的分布式解决方案,在3.0版本正式推出,有效地解决了Redis分布式方面的需求。

1. 数据分布

1.1 数据分布理论

    分布式数据库把数据集划分到多个节点上,每个节点负责整体数据的一个子集。需要重点关注的是数据分区规则。常见的分区规则有如下几种。

    1.节点取余分区

    使用特定的数据,如Redis的键,再根据节点数量N使用公式:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。

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

    2.一致性哈希分区

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

    优点:加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。

    缺点:不适合少量数据节点的分布式方案;会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。在增减节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡。

    3.虚拟槽分区

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

一个物理节点有多个槽,一个槽里可以存储多个key。类似于Java中从ConcurrentHashMap,一个物理节点类似一个map,一个槽类似map中的一个segment,key类似segment中管理的键值对。

1.2 集群功能限制

    1)key批量操作支持有限。如mset、mget,只支持具有相同slot值的key执行批量操作。

    2)key事务操作支持有限。只支持多key在同一节点上的事务操作。

    3)key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash、list等映射到不同的节点。

    4)不支持多数据库空间。只能使用一个数据库空间,即db0。

    5)复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。

2. 搭建集群

    2.1 准备节点

    假设在同一机器上创建6个进程,数据节点配置文件如下:

port 6379 # 节点端口
cluster-enabled yes		#开启集群模式
cluster-node-timeout 15000		#节点超时时间,单位毫秒
cluster-config-file "nodes-6379.conf"	#集群内部配置文件,主要记录集群中所有数据节点、槽分布等信息
#启动6个进程
redis-server conf/redis-6379.conf
redis-server conf/redis-6380.conf
redis-server conf/redis-6381.conf
redis-server conf/redis-6382.conf
redis-server conf/redis-6383.conf
redis-server conf/redis-6384.conf

    nodes-6379.conf文件分析

    第一次启动时如果没有集群配置文件,它会自动创建一份。如果启动时存在集群配置文件,节点会使用配置文件内容初始化集群信息。当集群内节点信息发生变化,如添加节点、节点下线、故障转移等。节点会自动保存集群状态到配置文件中。

    注意:Redis自动维护集群配置文件,不要手动修改。

    节点ID,它是一个40位16进制字符串,用于唯一标识集群内一个节点,之后很多集群操作都要借助于节点ID来完成。需要注意是,节点ID不同于运行ID。节点ID在集群初始化时只创建一次,节点重启时会加载集群配置文件进行重用,而Redis的运行ID每次重启都会变化。

    当集群第一次启动时,每个节点目前只能识别出自己的节点信息。如上面我们启动6个节点,但每个节点彼此并不知道对方的存在。

2.2 节点握手

    节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信,达到感知对方的过程。节点握手是集群彼此通信的第一步,由客户端发起命令:cluster meet{ip}{port}。

    如在6379节点上执行:

redis-cli -p 6379  cluster meet 127.0.0.1 6380
redis-cli -p 6379  cluster meet 127.0.0.1 6381
redis-cli -p 6379  cluster meet 127.0.0.1 6382
redis-cli -p 6379  cluster meet 127.0.0.1 6383
redis-cli -p 6379  cluster meet 127.0.0.1 6384

    我们只需要在集群内任意节点上执行cluster meet命令加入新节点,握手状态会通过消息在集群内传播,这样其他节点会自动发现新节点并发起握手流程。最后执行cluster nodes命令确认6个节点都彼此感知并组成集群。

2.3 分配槽,配置主从

    Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过cluster addslots命令为节点分配槽。

#把16384个slot平均分配给6379、6380、6381三个节点。
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  #集群可用,如果没有分配槽,这里会显示fail
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
#查看槽分配情况
127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 master - 0 146807624012 5 connected
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 master - 0 146807623962 4 connected
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 master - 0 146807624062 3 connected
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0      connected 0-5461
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 146807623760  1 connected  5462-10922
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 146807623861 2  connected  10923-16383

        目前还有三个节点没有使用,作为一个完整的集群,每个负责处理槽的节点应该具有从节点,保证当它出现故障时可以自动进行故障转移。集群模式下,Reids节点角色分为主节点和从节点。首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关的数据。使用clusterreplicate{nodeId}命令让一个节点成为从节点。其中命令执行必须在对应的从节点上执行,nodeId是要复制主节点的节点ID。

#为主节点分配从节点,需要在每个从节点中执行。
127.0.0.1:6382>cluster replicate cfb28ef1deee4e0fa78da86abe5d24566744411e
OK
127.0.0.1:6383>cluster replicate 8e41673d59c9568aa9d29fb174ce733345b3e8f1
OK
127.0.0.1:6384>cluster replicate 40b8d09d44294d2e23c7c768efc8fcd153446746
OK
#最后查看集群状态
127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 slave 40b8d09d44294d2e23c7c768efc8fcd153446746 0 1468076865939 5 connected
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 slave 8e41673d59c9568aa9d29fb174ce733345b3e8f1 0 1468076868966 4 connected
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 slave cfb28ef1deee4e0fa78da86abe5d24566744411e 0 1468076869976 3 connected
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected 0-5461
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 146807687098 connected 5462-10922
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 146807686795 connected 10923-16383

       目前为止,我们依照Redis协议手动建立一个集群。它由6个节点构成,3个主节点负责处理槽和相关数据,3个从节点负责故障转移。

2.4 用redis-trib.rb搭建集群

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

    1.Ruby环境准备

#安装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
cd ruby-2.3.1
./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/bin
#安装rubygem redis依赖
wget http:// rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
gem list --check redis gem
#安装redis-trib.rb
sudo cp /{redis_home}/src/redis-trib.rb /usr/local/bin
#验证是否安装成功
redis-trib.rb
Usage: redis-trib <command> <options> <arguments ...>
    create          host1:port1 ... hostN:portN
                  --replicas <arg>
    check           host:port
    info            host:port

     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 --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
#create  创建集群
#--replicas    指定集群中每个主节点配备几个从节点,这里设置为1
#创建过程中首先会给出主从节点角色分配的计划
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:6481 #主节点
127.0.0.1:6482 #主节点
127.0.0.1:6483 #主节点
Adding replica 127.0.0.1:6484 to 127.0.0.1:6481 #从节点对应的主节点
Adding replica 127.0.0.1:6485 to 127.0.0.1:6482 #从节点对应的主节点
Adding replica 127.0.0.1:6486 to 127.0.0.1:6483 #从节点对应的主节点
M: 869de192169c4607bb886944588bc358d6045afa 127.0.0.1:6481
slots:0-5460 (5461 slots) master #主节点槽分配
M: 6f9f24923eb37f1e4dce1c88430f6fc23ad4a47b 127.0.0.1:6482
slots:5461-10922 (5462 slots) master #主节点槽分配
M: 6228a1adb6c26139b0adbe81828f43a4ec196271 127.0.0.1:6483
slots:10923-16383 (5461 slots) master #主节点槽分配
S: 22451ea81fac73fe7a91cf051cd50b2bf308c3f3 127.0.0.1:6484
replicates 869de192169c4607bb886944588bc358d6045afa
S: 89158df8e62958848134d632e75d1a8d2518f07b 127.0.0.1:6485
replicates 6f9f24923eb37f1e4dce1c88430f6fc23ad4a47b
S: bcb394c48d50941f235cd6988a40e469530137af 127.0.0.1:6486
replicates 6228a1adb6c26139b0adbe81828f43a4ec196271
Can I set the above configuration (type 'yes' to accept):
#当我们同意这份计划之后输入yes,redis-trib.rb开始执行节点握手和槽分配操作
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join..
>>> Performing Cluster Check (using node 127.0.0.1:6481)
... 忽略 ...
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered. #16384个槽全部被分配,集群创建成功

    注意:给redis-trib.rb的节点地址必须是不包含任何槽/数据的节点,否则会拒绝创建集群。

    4.集群完整性检查

    集群完整性指所有的槽都分配到存活的主节点上,只要16384个槽中有一个没有分配给节点则表示集群不完整。可以使用redis-trib.rb check命令检测。

redis-trib.rb check 127.0.0.1:6481  #check命令只需要给出集群中任意一个节点地址就可以完成整个集群的检查工作
#如果返回如下结果,表示所有槽都被分配完毕
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

3. 节点通信

    为什么我们在手动创建集群时的第二步中,只让某个节点执行多个meet操作后,整个集群中的所有节点都知道其他节点的存在了呢?

    在redis集群中每个节点需要维护各个节点元数据信息,包括节点负责哪些数据,是否出现故障等状态信息。然后采用Gossip协议让节点彼此不断通信交换信息,一段时间后所有的节点都会知道集群完整的信息。通信过程说明:

    1)集群中的每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号在基础端口上加10000。

    2)每个节点在固定周期内(定时任务每秒执行10次)通过特定规则选择几个节点(不是全部)发送ping消息。

    3)接收到ping消息的节点用pong消息作为响应。

    4)只要集群中的所有节点彼此可以正常通信,最终它们会达到一致的状态。

4. 集群伸缩

    集群伸缩原理:槽和对应数据在不同节点之间灵活移动。即通过相关命令把一部分槽和数据迁移给新节点或者将目标节点中的槽和数据迁移到集群其他主节点中来实现集群的伸缩。

4.1 集群扩容

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

    2)加入集群。

        用cluster meet命令加入到现有集群中。在集群内任意节点执行cluster meet命令让6385和6386节点加入进来。

#手动方式
redis-cli -p 6379 cluster meet 127.0.0.1 6385    
redis-cli -p 6379cluster meet 127.0.0.1 6386
#redis-trib.rb方式 建议用这种方式,因为他会做孤立节点的检测,如果节点为其他redis集群节点则会报错
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

      集群内新旧节点经过一段时间的ping/pong消息通信之后,所有节点会发现新节点并将它们的状态保存到本地。新节点刚开始都是主节点状态,但是由于没有负责的槽,所以不能接受任何读写操作。对于新节点的后续操作我们一般有两种选择:为它迁移槽和数据实现扩容;作为其他主节点的从节点负责故障转移。

     如果是作为其他节点的从节点负责故障转移时(如将6385作为6379从节点),执行redis-cli -p 6385 cluster replicate 6379节点的id

    3)迁移槽和数据。

    手动方式很复杂,直接用redis-trib.rb实现。

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。

    4) 为新主节点添加从节点:

    redis-cli -p 6386 cluster replicate 主节点6385id

4.2 集群缩容

    1)迁移槽和数据

    假设需要干掉端口为6385和6386这一对主从节点

#迁出槽
redis-trib.rb reshard 127.0.0.1:6379 --from 6385节点id --to 6379节点id --slots 槽数量
redis-trib.rb reshard 127.0.0.1:6379 --from 6385节点id --to 6380节点id --slots 槽数量
redis-trib.rb reshard 127.0.0.1:6379 --from 6385节点id --to 6381节点id --slots 槽数量
#查看6385的槽是否全部迁出,只有全部迁出才能让节点下线
redis-cli -p 6379 cluster nodes  #检查6385对应从槽是否全部迁移完毕

   2)忘记节点并关闭节点

#注意 在忘记节点时,需要先忘记从节点再忘记主节点,不然redis集群会进行故障转移
redis-trib.rb del-node 127.0.0.1:6379 6386从节点id
redis-trib.rb del-node 127.0.0.1:6379 6385主节点id

5. 集群下的客户端

5.1 请求重定向(move异常)

    在集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重定向。

redis-cli -p 6379 get key  #如果key1在6379节点上,则正常返回,否则异常返回并提示key所在的节点
redis-cli -c -p 6379 get key #如果key1在6379节点上,则正常返回,否则重定向到key所在的节点,执行get操作,并返回结果。 -c  是以集群方式操作

5.2 ask重定向

    Redis集群支持在线迁移槽(slot)和数据来完成水平伸缩,当slot对应的数据从源节点到目标节点迁移过程中,客户端需要做到智能识别,保证键命令可正常执行。例如当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分数据在源节点,而另一部分在目标节点。如果键在还未迁移到目标节点,则直接返回结果,如果键已经迁移到了目标节点,那么会返回一个ask重定向异常。

    ASK与MOVED虽然都是对客户端的重定向控制,但是有着本质区别。ASK重定向说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只能是临时性的重定向,客户端不会更新slots缓存。但是MOVED重定向说明键对应的槽已经明确指定到新的节点,因此需要更新slots缓存。

5.3 智能客户端-JedisCluster

    1. 基本使用

public JedisCluster(
    Set<HostAndPort> jedisClusterNode,#所有Redis Cluster节点信息(也可以是一部分,因为客户端可以通过cluster slots自动发现)
    int connectionTimeout, #连接超时
    insoTimeout, #读写超时。
    int maxAttempts,#重试次数。
    final GenericObjectPoolConfig poolConfig)#连接池参数,JedisCluster会为edis Cluster的每个节点创建连接池

#使用示例
//  初始化所有节点 ( 例如 6 个节点 )
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("10.10.xx.1", 6379));
jedisClusterNode.add(new HostAndPort("10.10.xx.2", 6379));
jedisClusterNode.add(new HostAndPort("10.10.xx.3", 6379));
jedisClusterNode.add(new HostAndPort("10.10.xx.4", 6379));
jedisClusterNode.add(new HostAndPort("10.10.xx.5", 6379));
jedisClusterNode.add(new HostAndPort("10.10.xx.6", 6379));
//  初始化 commnon-pool 连接池,并设置相关参数
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
//  初始化 JedisCluster
JedisCluster jedisCluster = new JedisCluster(jedisClusterNode, 1000, 1000, 5, poolConfig);
// 执行命令
jedisCluster.set("hello", "world");
jedisCluster.get("key");

对于JedisCluster的使用需要注意以下几点:

     JedisCluster包含了所有节点的连接池(JedisPool),所以建议JedisCluster使用单例。

     JedisCluster每次操作完成后,不需要管理连接池的借还,它在内部已经完成。

     JedisCluster一般不要执行close()操作,它会将所有JedisPool执行destroy操作。

    2. JedisCluster实现原理:

    1)在创建JedisCluster时,JedisCluster根据redis节点信息,获取集群中所有槽和节点的对应关系,并缓存到本地,同时为每个节点创建连接池。

    2)当指令到来时,如get key。JedisCluster计算该key对应是哪个槽,并从缓存中取出槽对应的节点连接池,并从池中获取一个连接执行指令。

    3)如果执行指令时连接出错(JedisConnectionException),那么随机找出一个活跃节点,执行命令。如果返回move异常,则重新初始化JedisCluster槽和节点对应关系缓存。

    4)向目标节点发送指令,目标节点返回结果,同时将连接归还给连接池。

    5)如果命令发送次数超过5次仍未成功,则抛出异常JedisClusterMaxRedirectionsException("Too many Cluster redirections")。节点宕机或请求超时都会抛出JedisConnectionException,导致触发了随机重试,当重试次数耗尽抛出这个错误。

    Redis集群支持自动故障转移,但是从故障发现到完成转移需要一定的时间,节点宕机期间所有指向这个节点的命令都会触发随机重试,每次收到MOVED重定向都会刷新本地缓存,并且每次都会抛出JedisClusterMaxRedirectionsException异常。

    3. JedisCluster多节点操作

     比如执行mget、mset等批量操作时,大量的key可能不在同一个节点中,因此需要手动或者自己封装相关操作(JedisCluster中未提供相关封装),封装操作的步骤如下:

    1)从JedisCluster实例获取槽和节点对应信息

    2)从JedisCluster实例获取节点对应的jedis连接池

    3)根据key计算对应的槽,再根据槽获取对应的节点,再根据节点对应的连接池获取具体连接,最后执行指令。

    4)在final中归还连接到连接池中

    实现方式一般有如下几种:

    1)串行get。将所有的key分成一个一个的key,一次执行一个get,最后组装结果返回。

    2)串行mget。将所有key进行分类,将同一个节点的key组装成一个mget,一次执行一个mget,最后组装返回结果。

    3)并行mget。将所有key进行分类,将同一个节点的key组装成一个mget,利用多线程的方式并行发送mget,最后组装返回结果。

6. 故障转移

   6.1 故障发现

    Redis集群内节点通过ping/pong消息实现节点通信,消息不但可以传播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。

    主观下线:集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果在cluster-node-timeout时间内通信一直失败,则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。

    客观下线:集群内大多数节点都认为某节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。

 6.2 故障转移步骤

    1)资格检查:每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障的主节点。如果从节点与主节点断线时间超过cluster-node-timeout*cluster-slave-validity-factor(默认为10),则当前从节点不具备故障转移资格。

    2)准备选举时间:所有从节点,根据复制偏移量进行排序,偏移量最大的排在前面

    3)发起选举:按照偏移量从大到小是顺序向集群中的master发起拉票请求,一般偏移量大的会获得更多的票。

    4)选举投票:master接收到拉票请求时,如果票还没有使用,则同意投票,否则不同意。

    5)替换主节点:当从节点收集到足够的选票之后,触发替换主节点操作:

        a)当前从节点变为主节点。

        b)删除故障主节点负责的槽,并把这些槽委派给自己。

        c)向集群广播当前从节点变为主节点并接管了故障主节点的槽信息。

6.3 故障转移时间

    主观下线识别时间=cluster-node-timeout(默认15秒)

    客观下线识别时间<=cluster-node-timeout/2

    从节点转移时间<=1000毫秒

    故障转移时间(23秒)<=主观下线识别时间+客观下线识别时间+从节点转移时间

7. 集群运维

7.1 集群完整性

    cluster-require-full-coverage参数默认为yes,表示只有集群中的16384个槽都被分配后,集群才可用。对于大多数业务无法容忍这种情况,因此建议将参数cluster-require-full-coverage配置为no,当主节点故障时只影响它负责槽的相关命令执行,不会影响其他主节点的可用性。

7.2 带宽消耗

    集群内节点消息通信本身会消耗带宽,官方建议集群最大规模在1000以内。集群内所有节点通过ping / pong消息彼此交换信息,节点间消息通信对带宽的消耗体现在以下几个方面:

    1)消息发送频率:跟cluster-node-timeout密切相关,当节点发现与其他节点最后通信时间超过cluster-node-timeout/2时会直接发送ping消息。

    2)消息数据量:每个消息主要的数据占用包含:slots槽数组(2KB空间)和整个集群1/10的状态数据(10个节点状态数据约1KB)

    3)节点部署的机器规模:机器带宽的上线是固定的,因此相同规模的集群分布的机器越多每台机器划分的节点越均匀,则集群内整体的可用带宽越高。

    集群带宽消耗主要分为:读写命令消耗+节点消息通信消耗,因此需要合理规划集群:

    1)在满足业务需要的情况下尽量避免大集群。同一个系统可以针对不同业务场景拆分使用多套集群。

    2)适度提高cluster-node-timeout降低消息发送频率,同时cluster-node-timeout还影响故障转移的速度,因此需要根据自身业务场景兼顾二者的平衡。

    3)如果条件允许集群尽量均匀部署在更多机器上。尽量避免一台机器部署多个redis节点。

7.3 发布订阅

    在集群模式下publish命令会向所有的节点进行广播,造成每条publish数据都会在集群内所有节点传播一次,加重带宽负担。如果非要使用发布订阅功能,建议单独走一套redis sentinel。

7.4 数据倾斜

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

    1. 数据倾斜

    1)节点和槽分配严重不均。可以使用redis-trib.rb info{host:ip}进行定位。当节点对应槽数量不均匀时,可以使用redis-trib.rb rebalance命令进行平衡。

    2)不同槽对应键数量差异过大。键通过CRC16哈希函数映射到槽上,正常情况下槽内键数量会相对均匀。但当大量使用hash_tag时,会产生不同的键映射到同一个槽的情况。通过命令:cluster countkeysinslot {slot}可以获取槽对应的键数量,识别出哪些槽映射了过多的键。再通过命令cluster getkeysinslot{slot}{count}循环迭代出槽下所有的键。从而发现过度使用hash_tag的键。

    3)bigkey问题。可以使用redis-cli --bigkeys命令识别bigkey,针对bigkey,可以根据业务进行拆分。同时槽迁移时,过大的bigkey也容易导致迁移超时。

    4)内存相关配置不一致。内存相关配置指hash-max-ziplist-value、set-max-intset-entries等压缩数据结构配置。当集群大量使用hash、set等数据结构时,如果内存压缩数据结构配置不一致,极端情况下会相差数倍的内存,从而造成节点内存量倾斜。

    2. 请求倾斜

    1)合理设计键,热点大集合对象做拆分或使用hmget替代hgetall避免整体读取。

    2)不要使用热键作为hash_tag,避免映射到同一槽。

    3)对于一致性要求不高的场景,客户端可使用本地缓存减少热键调用。

7.5 读写分离

    集群模式下从节点不接受任何读写请求,发送过来的键命令会重定向到对应的主节点上。

    可以使用readonly命令打开客户端连接只读状态。readonly命令是连接级别生效,因此每次新建连接时都需要执行readonly启只读状态。

    集群模式下读写分离成本比较高,可以直接扩展主节点数量提高集群性能,一般不建议集群模式下做读写分离。

7.6 数据迁移

    应用Redis集群时,常需要把单机Redis数据迁移到集群环境。redis-trib.rb工具提供了导入功能,用于数据从单机向集群环境迁移的场景,redis-trib.rb import host:port --from <arg> --copy --replace。但这种方式有很多缺点,不建议使用。建议使用开源工具redis-migrate-tool(唯品会)。

总结:

1)redis cluster 数据分区采用的是虚拟槽方式(16384个槽),每个节点负责一部分槽,每个槽里存储多个key。最终实现数据和请求的负载均衡

2)搭建集群有4个步骤:准备节点、节点握手、分配槽、主从复制。建议使用redis-trib.rb工具快速搭建集群。

3)集群内部节点通信采用Gossip协议彼此发送消息,消息类型分为:ping消息、pong消息、meet消息、fail消息等。节点定期不断发送和接受ping/pong消息来维护更新集群的状态。消息内容包括节点自身数据和部分其他节点的状态数据。

4)集群伸缩通过在节点之间移动槽和相关数据实现。扩容时根据槽迁移计划把槽从源节点迁移到目标节点,源节点负责的槽相比之前变少从而达到集群扩容的目的,收缩时如果下线的节点有负责的槽需要迁移到其他节点,再通过cluster forget命令让集群内其他节点忘记被下线节点。

5)jedis cluster 针对单key操作的封装比较完善,使用简单;但对多key的操作,需要自行封装。

6)集群自动故障转移过程分为故障发现和故障恢复。节点下线分为主观下线和客观下线,当超过半数主节点认为故障节点为主观下线时标记它为客观下线状态。从节点负责对客观下线的主节点触发故障恢复流程,保证集群的可用性。

7)开发和运维集群过程中常见问题包括:超大规模集群带宽消耗,pub/sub广播问题,集群节点倾斜问题,在线迁移数据等。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值