Redis cluster 小结

1 数据分布
1.1 数据分布理论

分布式数据库首先要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集

常见的分区规则有哈希分区和顺序分区两种,区别如下

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

这里重点介绍哈希分区

  1. 节点取余分区

    使用特定的数据,比如Redis的key或者用户id,再根据节点数量N使用公式hash(key)%N, 用来决定数据映射到哪个节点上,这种方式存在一个问题,当节点数量变化时,比如收缩或者扩容节点,数据映射关系变化,会导致数据的迁移,优点是足够简单,节点取余算法常用于数据库的分库分表规则,采用预分区的方式,比如划分512张表,保证可以支撑未来一段时间的数据量,之后再根据负载进行迁移

  2. 一致性哈希分区

    一致性哈希分区实现思路是为系统中每个节点分配一个token,范围一般在0-2的32次方,这些token构成一个哈希环,数据读写执行节点查找时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点

  3. 虚拟槽分区

    虚拟槽分区巧妙使用了哈希空间,使用分散度良好的哈希函数把所有的数据映射到一个固定范围的整数集合中,整数定义为槽,这个范围一般远远大于节点数,比如Redis Cluster槽的范围是0 - 16383,槽是集群内数据管理和迁移的基本单位

1.2 Redis数据分区

Redis Cluster采用虚拟槽分区,把所有key按照哈希函数映射到0-16383整数槽内,计算公式slot=CRC16(key) & 16383 ,每个节点负责维护一部分槽和部分数据

在这里插入图片描述

Redis虚拟槽分区的特点

  • 解耦数据和节点之间的关系,简化了节点扩容和收缩的难度
  • 节点自身维护槽的映射关系,不需要客户端或者代理服务器维护槽分区元数据
  • 支持节点,槽,键之间的映射查询,用户数据路由,在线伸缩等场景
1.3 集群功能限制
  1. key批量操作支持有限。比如mset, mget,目前只支持具有相同slot值的key执行批量操作
  2. key事务操作支持有限,同理只支持多key在同一节点上的事务操作,当多个key分布在不同节点上时无法使用事务功能
  3. key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash,list等映射到不同的节点
  4. 不支持多数据库空间,单机下的redis可以支持16个数据库,集群模式下只能使用一个数据库空间db0
  5. 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构
2 搭建集群

介绍完Redis集群分区规则后,开始手动搭建Redis集群,需要以下三个步骤

  1. 准备节点
  2. 节点握手
  3. 分配槽
2.1 准备节点

集群相关配置如下

# 端口
port 6379
# 开启集群模式
cluster-enabled yes
# 节点超时时间,单位毫秒
cluster-node-timeout 15000
# 配置未见地址
cluster-config-file "nodes-6379.conf"

其他配置和单机模式下配置一致即可,配置文件命名规则redis-{port}.conf,准备好配置后启动所有节点

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

查看节点日志是否正确,日志内容如下

cat log/redis-6379.log 
* No cluster configuration found, I'm cfb28ef1deee4e0fa78da86abe5d24566744411e # Server started, Redis version 6.0.8
* The server is now ready to accept connections on port 6379

6379节点启动成功,第一次启动没有集群配置文件,它会自动创建一份,文件名使用cluster-config-file控制,如果启动时存在集群配置文件,节点使用配置文件初始化集群信息,当集群内节点信息发生变化,如添加节点,节点下线,故障转移等,节点会自动保存集群状态到配置文件中,Redis自动维护集群配置文件,不要手动修改,防止节点重启时产生集群信息错乱

如节点6379首次启动后生成集群配置如下

cat data/nodes-6379.conf 
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected vars currentEpoch 0 lastVoteEpoch 0

文件内容记录集群初始状态,这里最重要的是节点ID,用于标识集群内的一个节点,之后很多集群操作都要通过节点ID完成,节点ID在集群初始化时只创建一次

2.2 节点握手

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

127.0.0.1:6379>cluster meet 127.0.0.1 6380 
127.0.0.1:6379>cluster meet 127.0.0.1 6381 
127.0.0.1:6379>cluster meet 127.0.0.1 6382 
127.0.0.1:6379>cluster meet 127.0.0.1 6383 
127.0.0.1:6379>cluster meet 127.0.0.1 6384

通过cluster nodes可以确认6个节点都彼此感知并且组成集群

127.0.0.1:6379> cluster nodes 
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 master - 0 1468073975551 5 connected 
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected 
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 master - 0 1468073978579 4 connected 
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 master - 0 1468073980598 3 connected 
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1468073974541 1 connected 
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1468073979589 2 connected

节点握手后集群还不能正常工作,这时集群处于下线状态,因为槽还没有被分配,只有当16384个槽全部分配给节点后,集群才进入在线状态,握手完成之后的状态如下
在这里插入图片描述

2.3 分配槽

Redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固定的槽,只有当节点分配了槽,才能相应和这些槽相关联的键命令。通过cluster addslots命令为节点分配槽。这里利用bash特性批量设置槽

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}

​ 把16384个slot平均分配给6379,6380,6381三个节点,执行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

执行cluster nodes查看节点和槽的分配关系

127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 master - 0 1468076240123 5 connected 
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected 0-5461
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 master - 0 1468076239622 4 connected
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 master - 0 1468076240628 3 connected 
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1468076237606 579
1 connected 5462-10922 
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1468076238612 2 connected 10923-16383

目前还有三个节点没有使用,他们分别作为6379,6380,6381的从节点,保证当主节点出现故障时可以自动进行故障转移,使用cluster replicate {nodeId} 命令让一个节点成为从节点

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

槽分配完成并且复制完成后,整个集群的结构如下
在这里插入图片描述

使用cluster nodes查看节点信息如下

127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 slave 40b8d09d44294d2e2 3c7c768efc8fcd153446746 0 1468076865939 5 connected cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected 0-5461
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 slave 8e41673d59c9568aa 9d29fb174ce733345b3e8f1 0 1468076868966 4 connected 40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 slave cfb28ef1deee4e0fa 78da86abe5d24566744411e 0 1468076869976 3 connected 8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1468076870987 1 connected 5462-10922 
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1468076867957 2 connected 10923-16383

目前为止我们按照Redis协议手动建立了一个集群,由6个节点组成,3个主节点组分处理槽和相关数据,3个从节点负责负责故障转移,但是过程过于复杂,因此Redis官方提供了redis-cli 命令工具方便我们快速搭建集群

2.4 使用redis-cli 搭建集群

Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb的支持(手动命令行添加集群的方式不变),集合到redis-cli里,避免了再安装ruby的相关环境。直接使用redis-cli的参数–cluster 来取代

2.4.1 跟之前一样准备好六个节点并启动
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
2.4.2 创建集群

启动好6个节点之后,使用redis-cli --cluster create命令完成节点握手和槽分配过程,命令如下:

redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1

–cluster-replicas参数指定集群中每个主节点配备几个从节点,这里设置为1。我们出于测试目的使用本地IP地址127.0.0.1,如果部署节点使用不同的IP地 址,redis-cli会尽可能保证主从节点不分配在同一机器下,因此会重新排序节点列表顺序。节点列表顺序用于确定主从角色, 先主节点之后是从节点。创建过程中首先会给出主从节点角色分配的计划,如下所示。

>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:6383 to 127.0.0.1:6379
Adding replica 127.0.0.1:6384 to 127.0.0.1:6380
Adding replica 127.0.0.1:6382 to 127.0.0.1:6381
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
   slots:[0-5460] (5461 slots) master
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
   slots:[5461-10922] (5462 slots) master
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
   slots:[10923-16383] (5461 slots) master
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
   replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
   replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
   replicates 952cb6be80998a86c95d273b04031de1fd5afffa
Can I set the above configuration? (type 'yes' to accept): yes

当我们同意这份计划之后输入yes,开始执行节点握手和槽分配操作,输出如下:

>>> 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:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
   slots: (0 slots) slave
   replicates 952cb6be80998a86c95d273b04031de1fd5afffa
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
   slots: (0 slots) slave
   replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
   slots: (0 slots) slave
   replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

最后的输出报告说明:16384个槽全部被分配,集群创建成功。这里需要注意给redis-cli的节点地址必须是不包含任何槽/数据的节点,否则会拒绝创建集群

cluster nodes查看节点信息

127.0.0.1:6379> cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602248698000 3 connected 10923-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602248699825 3 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602248696000 1 connected 0-5460
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602248697733 1 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602248699000 2 connected 5461-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602248697000 2 connected
3 节点通信
3.1 通信流程

在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责那些数据,是否出现故障等状态信息。常见的元数据维护方式分为两种:集中式和P2P方式。Redis采用P2P的Gossip协议,Gossip的工作原理就是节点彼此不断通信交换信息,一段时间后所有节点都会知道集群完整信息,类似流言的方式
在这里插入图片描述

  1. 每个节点都会单独开辟一条tcp通道,用于节点之间彼此通信,通信端口号在基础端口号上加上10000
  2. 每个节点在固定周期内通过特定规则向部分节点发送ping消息
  3. 接收到ping消息的节点用pong消息作为相应

常用的Gossip消息可分为:ping消息、pong消息、meet消息、fail消息等

3.2 节点选择

Redis集群内节点通信采用固定频率(每秒十次),因此选择需要通信的节点列表显得尤为重要,通信节点选择过多虽然可以及时交换信息,但是会加重带宽和计算的消耗,选择过少会影响节点之间交换信息的速率,从而影响故障发现,新节点发现等需求的速度,因为Gossip需要兼顾信息交换实时性和成本开销

Redis集群内节点维护定时任务默认每秒十次,每秒会随机选出五个节点,找到最久没有通信的节点发送ping消息,用户保证Gossip的随机性。每100毫秒会扫描本地节点列表,如果发现节点最近一次接收pong消息的时间大于cluster-node-time/2, 则立刻发送ping消息,防止该节点信息太长时间未更新。根据以上规则,每个节点每秒需要发送ping消息的数据为1+10 *num(node.pong_received > cluster-node-time/2), 因此cluster-node-time的配置非常重要

4 集群伸缩

Redis集群提供了灵活的节点扩容和收缩方案,在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容,如下
在这里插入图片描述

Redis集群可以实现对节点的灵活上下限控制,其中原理可以抽象为槽和对应数据在不同节点之间灵活移动,首先来看之前搭建的集群槽和数据与节点的对应关系
在这里插入图片描述

三个主节点分别维护自己负责的槽和对应数据,如果希望加入一个节点实现集群扩容,需要通过相关命令把一部分槽和数据迁移给新节点,如下
在这里插入图片描述

每个节点把一部分槽和数据迁移到新的节点6385,每个节点负责的槽和数据比之前少了从而达到了集群口容的目的

4.1 扩容集群

手动扩容集群分为以下三个步骤, 手动扩容集群只简单讲述步骤

  1. 准备新节点
  2. 加入集群
  3. 迁移槽和数据
4.1.1 准备新节点,并且启动节点
redis-server conf/redis-6385.conf 
redis-server conf/redis-6386.conf
4.1.2 加入集群
127.0.0.1:6379> cluster meet 127.0.0.1 6385
127.0.0.1:6379> cluster meet 127.0.0.1 6386

加入集群之后可以通过cluster nodes查看新加入的节点信息,此时节点结构为
在这里插入图片描述

redis-cli工具也实现了为现有集群添加新节点的命令

$ redis-cli --cluster add-node 127.0.0.1:6385 127.0.0.1:6379 
>>> Adding node 127.0.0.1:6385 to cluster 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
   slots: (0 slots) slave
   replicates 952cb6be80998a86c95d273b04031de1fd5afffa
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
   slots: (0 slots) slave
   replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
   slots: (0 slots) slave
   replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:6385 to make it join the cluster.
[OK] New node added correctly.

$ redis-cli --cluster add-node 127.0.0.1:6386 127.0.0.1:6379
>>> Adding node 127.0.0.1:6386 to cluster 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
   slots: (0 slots) slave
   replicates 952cb6be80998a86c95d273b04031de1fd5afffa
M: ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385
   slots: (0 slots) master
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
   slots: (0 slots) slave
   replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
   slots: (0 slots) slave
   replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:6386 to make it join the cluster.
[OK] New node added correctly

正式环境建议使用redis-cli --cluster add-node 命令加入新节点,该命令内部会执行新节点状态检查,如果新节点已经加入其他集群或者包含数据,则放弃加入集群打印信息

4.1.3 迁移槽和数据

(1)槽迁移计划,槽是Redis集群管理数据的基本单位,首先需要为新节点制定槽的迁移计划,确定原有节点的那些槽需要迁移到新节点,如下,原来集群有三个主节点,6379负责5461个槽,6381,6382分别负责5460个槽,现在扩容多了一个主节点,每个节点需要负责4096个槽,那么6379节点需要迁移1365个槽到6385,相应的数据也要对应迁移,另外两个节点也类似
在这里插入图片描述

(2) 迁移数据

手动迁移流程说明

  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}命令,通知槽分配给目标节点。为了保证槽节点映射变更及时传播,需要遍历发送给所有主节点更新被迁移的槽指向新节点。

手动执行命令演示槽迁移过程,是为了更好理解迁移流程,实际操作时要涉及大量槽和非常多的key,redis-cli提供了槽迁移重分片功能,可以内部完成这些事情

命令如下:

$ redis-cli --cluster reshard 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
   slots: (0 slots) slave
   replicates 952cb6be80998a86c95d273b04031de1fd5afffa
M: 90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386
   slots: (0 slots) master
M: ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385
   slots: (0 slots) master
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
   slots: (0 slots) slave
   replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
   slots: (0 slots) slave
   replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 4096

打印集群每个节点信息后,需要确认迁移的槽数量,这里数据4096个

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

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

What is the receiving node ID? ece23a03616beb2f133ef45a93d20966de0d6e78

之后输入源节点的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: 5ae57a7bf02314899758adb2cc60efe04a4a2caa
Source node #2: 952cb6be80998a86c95d273b04031de1fd5afffa
Source node #3: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
Source node #4: done

槽迁移完成后,reshard命令自动推出,执行cluster nodes可以查看节点和槽映射的变化

127.0.0.1:6379> cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602313309000 3 connected 12288-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602313308000 3 connected
90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386@16386 master - 0 1602313309945 7 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602313310000 1 connected 1365-5460
ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385@16385 master - 0 1602313310000 8 connected 0-1364 5461-6826 10923-12287
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602313309000 1 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602313312025 2 connected 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602313310985 2 connected

槽6385负责的槽变为0-1364 5461-6826 10923-12287。由于槽用于hash运算本身顺序没有什么意义,因此无需强制要求节点负责槽的顺序性,迁移之后建议使用redis-cli --cluster rebalance命令检查节点之间槽的均衡性,命令如下

$ redis-cli --cluster  rebalance 127.0.0.1:6379
>>> Performing Cluster Check (using node 127.0.0.1:6379)
[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.00% threshold.

最后添加从节点,把6386节点添加为6385的从节点,此时整个集群扩容完成

127.0.0.1:6386> cluster replicate ece23a03616beb2f133ef45a93d20966de0d6e78
OK
127.0.0.1:6386> cluster nodes
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602313782997 3 connected
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602313782000 3 connected 12288-16383
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602313786117 2 connected 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602313783000 2 connected
ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385@16385 master - 0 1602313784037 8 connected 0-1364 5461-6826 10923-12287
90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386@16386 myself,slave ece23a03616beb2f133ef45a93d20966de0d6e78 0 1602313783000 8 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 master - 0 1602313783000 1 connected 1365-5460
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602313785077 1 connected
4.2 收缩集群

收缩集群意味着缩减规模,需要从现有集群中安全下线部分节点,安全下线节点流程如下
在这里插入图片描述

流程说明

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

这里下线6385,6385是上一节刚刚加入的主节点,6386是它的从节点,下线6385之前,需要把它所负责的槽迁移到其他节点

如下所示, 总共需要执行三次reshard,分别迁移1365,1365,1366个槽到6379,6380,6381节点中去,这里采用某次的reshard作为示例

$ redis-cli --cluster reshard 127.0.0.1:6385
>>> Performing Cluster Check (using node 127.0.0.1:6385)
M: ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385
   slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master
   1 additional replica(s)
S: d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383
   slots: (0 slots) slave
   replicates eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db
M: 952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381
   slots:[12288-16383] (4096 slots) master
   1 additional replica(s)
M: 5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379
   slots:[1365-5460] (4096 slots) master
   1 additional replica(s)
M: eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380
   slots:[6827-10922] (4096 slots) master
   1 additional replica(s)
S: 358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384
   slots: (0 slots) slave
   replicates 952cb6be80998a86c95d273b04031de1fd5afffa
S: 90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386
   slots: (0 slots) slave
   replicates ece23a03616beb2f133ef45a93d20966de0d6e78
S: 26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382
   slots: (0 slots) slave
   replicates 5ae57a7bf02314899758adb2cc60efe04a4a2caa
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 1365
What is the receiving node ID? 5ae57a7bf02314899758adb2cc60efe04a4a2caa
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: ece23a03616beb2f133ef45a93d20966de0d6e78
Source node #2: done

迁移完成后执行cluster nodes可以发现6385不再持有槽

cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602318334277 11 connected 6826 10923-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602318332201 11 connected
90be5b0642169f6b0f7ca7d1afcb569176923713 127.0.0.1:6386@16386 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602318333000 11 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602318332000 9 connected 0-5460
ece23a03616beb2f133ef45a93d20966de0d6e78 127.0.0.1:6385@16385 master - 0 1602318333241 8 connected
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602318332000 9 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602318331000 10 connected 5461-6825 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602318332000 10 connected

4.2.2 忘记节点

Redis提供了cluster forget {downNodeId}实现该功能,但是需要在所有主节点执行此命令,当节点收到命令后,会把nodeId指定的节点加入禁用列表中,禁用列表内的节点不再发送Gossip消息,禁用列表有效期是60s,超过60s会再次参与消息交换,也就是说当第一次forget命令发出后,我们有60s时间让集群内所有节点忘记下线节点,线上不建议直接使用cluster forget命令,建议使用redis-cli --cluster del-node {host:port}{downNodeId}命令

~# redis-cli --cluster del-node 127.0.0.1:6379 ece23a03616beb2f133ef45a93d20966de0d6e78
>>> Removing node ece23a03616beb2f133ef45a93d20966de0d6e78 from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.
~# redis-cli --cluster del-node 127.0.0.1:6379 90be5b0642169f6b0f7ca7d1afcb569176923713
>>> Removing node 90be5b0642169f6b0f7ca7d1afcb569176923713 from cluster 127.0.0.1:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.

执行cluster nodes可以看到6385 6386已经从集群中移除

127.0.0.1:6379> cluster nodes
952cb6be80998a86c95d273b04031de1fd5afffa 127.0.0.1:6381@16381 master - 0 1602318717000 11 connected 6826 10923-16383
358e348c527e4ac577421b50c9d8c9af596c9811 127.0.0.1:6384@16384 slave 952cb6be80998a86c95d273b04031de1fd5afffa 0 1602318717921 11 connected
5ae57a7bf02314899758adb2cc60efe04a4a2caa 127.0.0.1:6379@16379 myself,master - 0 1602318718000 9 connected 0-5460
26732b84fc19db500942e3c782e094af38ce5f1f 127.0.0.1:6382@16382 slave 5ae57a7bf02314899758adb2cc60efe04a4a2caa 0 1602318718961 9 connected
eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 127.0.0.1:6380@16380 master - 0 1602318717000 10 connected 5461-6825 6827-10922
d7eb27ab8a409c21a7c6f21aa496b82125e873a5 127.0.0.1:6383@16383 slave eb095ba2bc45dd9c3dd03cc5d791c546cd34e5db 0 1602318715000 10 connected
5 故障转移

Redis集群自身实现了高可用。高可用首先需要解决集群部分失败的场景: 当集群中少量节点出现故障时通过自动故障转移保证集群可以正常对外提供服务。本届介绍故障转移的细节

5.1 故障发现
5.1.1 主线下线

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

5.1.2 客观下线

当某个节点判断另外一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。ping/pong消息的消息体会携带1/10的其他节点状态数据,当接收节点发现消息体中含有主观下线的节点状态时,会保存到本地下线报告链表中

通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点时主观下线时,触发客观下线流程,这里有两个问题

  1. 为什么必须时负责槽的主节点参与故障发现决策?因为集群模式下只有处理槽的主节点才负责读写请求和集群槽等关键信息维护,而从节点只进行主节点数据和状态信息的复制
  2. 为什么半数以上处理槽的主节点?必须半数以上时为了应对网络分区等原因造成的集群分割情况,被分割的小集群因为无法完成从主观下线到客观下线这一关键过程,从而防止小集群完成故障转移之后继续对外提供服务

如果在cluster-node-time*2时间内无法收集到一半以上槽节点的下线报告,那么之前的下线报告将会过期,也就是说主观下线上报的速度追赶不上下线报告过期的速度,那么故障节点将永远无法被标记为客观下线从而导致故障转移失败。因此不建议将cluster-node-time设置得过小

这里针对下线报告和尝试客观下线逻辑进行详细说明

(1) 维护下线报告列表

每个节点都存在一个下线链表结构,保存了其他主节点针对某节点的下线报告,下线报告中保存了报告故障的节点结构和最近收到的下线报告的时间,当接收到fail状态时,会维护对应节点的下线上报链表

每个下线报告都存在有效期,每次在尝试客观下线时,都会检测下线报告是否过期,对于过期的下线报告将被删除,如果在cluster-node-time*2 的时间内该下线报告没有得到更新则过期并删除

下线报告的有效期限是server.cluster_node_timeout*2,主要是针对故障 误报的情况。例如节点A在上一小时报告节点B主观下线,但是之后又恢复正常。现在又有其他节点上报节点B主观下线,根据实际情况之前的属于误

报不能被使用

(2)尝试客观下线

集群中的节点每次接收到其他节点的pfail状态,都会尝试促发客观下线

流程如下

在这里插入图片描述

流程说明:

1)首先统计有效的下线报告数量,如果小于集群内持有槽的主节点总数的一半则退出。

2)当下线报告大于槽主节点数量一半时,标记对应故障节点为客观下线状态。

3)向集群广播一条fail消息,通知所有的节点将故障节点标记为客观下线,fail消息的消息体只包含故障节点的ID。

5.2 故障恢复

故障恢复流程如下
在这里插入图片描述

  1. 资格检查

    每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障的从节点,如果从节点和主节点短线时间超过cluster-node-time*cluster-slave-validity-factor,则当前从节点不具备故障转移资格,参数cluster-slave- validity-factor用于从节点的有效因子,默认为10

  2. 准备选举时间

    当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该时间后才能执行后续流程

    这里之所以采用延迟触发机制,主要是通过对多个从节点使用不同的延迟选举时间来支持优先级问题。复制偏移量越大说明从节点延迟越低,那么它应该具有更高的优先级来替换故障主节点,如下图所示,复制便宜量最大的slaveb-1延迟1秒执行,保证复制延迟低的从节点有限发起选举

    在这里插入图片描述

  3. 发起选举

    当从节点定时任务检测到底故障选举时间(failover_auth_time)到达后,发起选举流程

    (1)更新配置纪元

    配置纪元是一个只增不减的整数,每个主节点自身维护一个配置纪元(clusterNode.configEpoch)标示当前主节点的版本,所有主节点的配置纪元都不相等,从节点会复制主节点的配置纪元。整个集群又维护一个全 局的配置纪元(clusterState.current Epoch),用于记录集群内所有主节点配置纪元的最大版本。执行 cluster info命令可以查看配置纪元信息

    127.0.0.1:6379> cluster info 
    ... cluster_current_epoch:15 // 整个集群最大配置纪元
    cluster_my_epoch:13 // 当前主节点配置纪元
    

    (2)广播选举消息

    在集群内广播选举消息(FAILOVER_AUTH_REQUEST),并记录已发送过消息的状态,保证该从节点在一个配置纪元内只能发起一次选举。消息内容如同ping消息只是将type类型变为FAILOVER_AUTH_REQUEST

  4. 选举投票

    只有持有槽的主节点才会处理故障选举消息(FAILOVER_AUTH_REQUEST),因为每个持有槽的节点在一个配置纪元内都有唯一的一张选票,当接到第一个请求投票的从节点消息时回复 FAILOVER_AUTH_ACK消息作为投票,之后相同配置纪元内其他从节点的选举消息将忽略。

    投票过程其实是一个领导者选举的过程,如集群内有N个持有槽的主节点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给一个从节点,因此只能有一个从节点获得N/2+1的选票,保证能够找出唯一的从节点。

    Redis集群没有直接使用从节点进行领导者选举,主要因为从节点数必须大于等于3个才能保证凑够N/2+1个节点,将导致从节点资源浪费。使用集群内所有持有槽的主节点进行领导者选举,即使只有一个从节点也可以完成选举过程。

    当从节点收集到N/2+1个持有槽的主节点投票时,从节点可以执行替换 主节点操作,例如集群内有5个持有槽的主节点,主节点b故障后还有4个,当其中一个从节点收集到3张投票时代表获得了足够的选票可以进行替换主节点操作, 如下所示

    在这里插入图片描述

  5. 替换主节点

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

    1)当前从节点取消复制变为主节点。

    2)执行clusterDelSlot操作撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽委派给自己。

    3)向集群广播自己的pong消息,通知集群内所有的节点当前从节点变

    为主节点并接管了故障主节点的槽信息。

5.3 故障转移时间

  1. 主观下线时间 cluster-node-timeout
  2. 主观下线状态消息传播时间<=cluster-node-timeout/2。消息机制对超过cluster-node-timeout/2未通信节点发起ping消息,消息体在选择包含那些节点时有限选取下线状态节点,所有通常这段时间内能够收集到半数以上主节点的pfail报告从而完成故障发现
  3. 从节点转移时间<=1000毫秒,鱿鱼存在延迟发起选举机制,偏移量最大的从节点会最多延迟一秒发起选举,通常第一次选举就会成功,所以从节点执行转移时间在1秒以内

根据以上分析可以预估故障转移时间

failover-time(毫秒) ≤ cluster-node-timeout + cluster-node-timeout/2 + 1000

6 运维注意点
6.1 集群完整性

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

6.2 带宽消耗

cluster-node-timeout 应根据线上环境合理配置,配置过小容易造成带宽消耗过大,另外集群节点数目过多,节点间通信也更加频繁,一般要注意三个点

  1. 在满足业务要求的情况下尽量避免超大集群
  2. 适当提高cluster-node-timeout降低资源消耗
  3. 如果条件允许集群尽量均匀部署在更多机器上
6.3 Pub/Sub广播

集群模式下内部实现对所有的publish命令都会向所有的节点进行广播,造成每条publish数据都会在集群内所有节点传播一次,加重带宽负担

当频繁应用

Pub/Sub功能时应该避免在大量节点的集群内使用,否则会严重消耗集群内网络带宽。针对这种情况建议使用sentinel结构专门用于Pub/Sub功能,从而规避这一问题

6.4 集群倾斜

集群倾斜指不同节点之间数据量和请求量出现明显差异,这种情况将加大负载均衡和开发运维的难度

6.4.1 数据倾斜

主要分为以下几种:

  • 节点和槽分配严重不均。 可以使用redis-cli --cluster rebalance 进行调整
  • 不同槽对应键数量差异过大。
  • 集合对象包含大量元素
  • 内存相关配置不一致
6.4.2 请求倾斜

集群内特定节点请求量/流量过大将导致节点之间负载不均,影响集群均衡和运维成本。常出现在热点键场景,当键命令消耗较低时如小对象的get、set、incr等,即使请求量差异较大一般也不会产生负载严重不均

避免方式如下:

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

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

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

6.5 集群读写分离
6.5.1 只读连接

可以使用readonly命令打开客户端连接只读状态,当开启只读状态时,从节点接收读命令处理流程变为:如果对应的槽属于自己正在复制的主节点则直接执行读命令,如果没有开启readonly,那么从节点接收到读请求将会重定向到主节点令,否则返回重定向信息

6.5.2 读写分离

集群模式下的读写分离,同样会遇到:复制延迟,读取过期数据,从节点故障等问题。针对从节点故障问题,客户

端需要维护可用节点列表,集群提供了cluster slaves{nodeId}命令,返回nodeId对应主节点下所有从节点信息

参考文献

Redis开发与运维

Redis 5.0 redis-cli --cluster help(https://www.cnblogs.com/zhoujinyi/p/11606935.html)

加大负载均衡和开发运维的难度

6.4.1 数据倾斜

主要分为以下几种:

  • 节点和槽分配严重不均。 可以使用redis-cli --cluster rebalance 进行调整
  • 不同槽对应键数量差异过大。
  • 集合对象包含大量元素
  • 内存相关配置不一致
6.4.2 请求倾斜

集群内特定节点请求量/流量过大将导致节点之间负载不均,影响集群均衡和运维成本。常出现在热点键场景,当键命令消耗较低时如小对象的get、set、incr等,即使请求量差异较大一般也不会产生负载严重不均

避免方式如下:

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

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

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

6.5 集群读写分离
6.5.1 只读连接

可以使用readonly命令打开客户端连接只读状态,当开启只读状态时,从节点接收读命令处理流程变为:如果对应的槽属于自己正在复制的主节点则直接执行读命令,如果没有开启readonly,那么从节点接收到读请求将会重定向到主节点令,否则返回重定向信息

6.5.2 读写分离

集群模式下的读写分离,同样会遇到:复制延迟,读取过期数据,从节点故障等问题。针对从节点故障问题,客户

端需要维护可用节点列表,集群提供了cluster slaves{nodeId}命令,返回nodeId对应主节点下所有从节点信息

参考文献

Redis开发与运维

Redis 5.0 redis-cli --cluster help(https://www.cnblogs.com/zhoujinyi/p/11606935.html)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值