(七)Redis的集群

思维导图

si

什么是Redis的集群?

  我们先了解下什么是集群?
  当单个服务器处理到达瓶颈的时候,将其复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就提升了好几倍(有瓶颈)。
  Redis的集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制故障转移功能,Redis集群至少需要6台Redis服务器

节点与集群

  一个节点就是一个普通的Redis服务器,一个Redis集群通常由多个节点组成,开始每个节点都是相互独立的,它们都处于一个只包含自己的集群当中。要组建一个真正可以工作的集群,必须将各个独立的节点连接起来,构成一个包含多个节点的集群。
  节点的信息都保存在自身的一个数据结构clusterNode当中,clusterNode结构保存了一个节点的当前状态,比如节点的创建时间、节点名字、节点当前的配置纪元、节点的IP地址和端口号、节点处理的槽等等信息。

启动节点

  普通的Redis服务器配置没有开启集群功能,自然也就不能说它是一个节点。既然这样,那我们就去配置文件开启集群开关,配置文件加入内容:

cluster-enabled yes  //打开集群模式
cluster-config-file nodes.conf  //设定节点配置文件名
cluster-node-timeout 5000  //设定节点超时时间,超过该时间集群自动进行主从切换

  下面为例子redis6380.conf文件的配置内容:

include /usr/local/etc/redis.conf
port 6380
pidfile "/var/run/redis_6380.pid"
dbfilename "dump6380.rdb"

cluster-enabled yes
cluster-config-file nodes6380.conf
cluster-node-timeout 5000

  配置完成后指定这个配置文件启动即可:

$ redis-server /usr/local/etc/redis6380.conf 
34342:C 13 Apr 2019 10:11:29.949 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
34342:C 13 Apr 2019 10:11:29.949 # Redis version=5.0.4, bits=64, commit=00000000, modified=0, pid=34342, just started
34342:C 13 Apr 2019 10:11:29.949 # Configuration loaded

  查看一下该进程,发现后面多了个[cluster]的标识,即说明该Redis服务器开启了集群模式(如果要连接该节点需要使用redis-cli -c - p <port>命令,表示该节点开启了集群)。

$ ps -ef|grep redis
  501 34343     1   0 10:11上午 ??         0:00.09 redis-server *:6380 [cluster] 

节点组成集群

  集群由多个节点组成,所以需要先将各个节点连接起来,连接各个节点的工作可以使用cluster meet命令来完成:

127.0.0.1:6379>cluster meet <IP> <Port>

  客户端连接节点后向另一个节点发送该命令可以让这两个节点进行握手,握手成功后,另一个节点就会被添加到发起命令节点所在的集群中,过程如下图:


节点的握手过程

  举个例子,启动3台Redis服务器(当前电脑6380、6381、6382、)后(注意正常集群至少需要6台Redis服务器,3主3从,此处只是实验),6380节点将6381和6382节点加入进自身集群中:

//启动完的6个节点
  501 32901     1   0 10:04下午 ??         0:00.09 redis-server *:6380 [cluster] 
  501 32910     1   0 10:04下午 ??         0:00.07 redis-server *:6381 [cluster] 
  501 32918     1   0 10:04下午 ??         0:00.07 redis-server *:6382 [cluster] 
//将6381和6382节点加入自身集群
$ redis-cli -c -p 6380
127.0.0.1:6380> cluster meet 127.0.0.1 6381
OK
127.0.0.1:6380> cluster meet 127.0.0.1 6382
OK

  具体过程如下图(名字不同,意思了解即可):


节点握手

节点通信消息

  集群中的各个节点通过发送和接收消息来进行通信,发送消息的节点为发送者,接收消息的节点为接收者。
  节点发送的消息主要为以下五种:

  • MEET消息:当发送者接到客户端发送的cluster meet命令时,发送者会向接收者发送MEET消息,请求接收者加入到发送者当前所处集群里;
  • PING消息:集群的每个节点默认每隔1秒钟就会从已知节点列表随机挑选5个节点,然后对这5个节点中最长时间没有发送过PING消息的节点发送PING消息,以此来检测被选中节点是否在线,其他特殊情况也会发送该消息;
  • PONG消息:接收者确认MEETPING消息已到达时,会向发送者发送该消息告知。特殊情况如故障转移成功后会向集群广播一条该消息;
  • FALL消息:当一个主节点A判断另一个主节点B已经进入FALL状态时,节点A会向集群广播B的FALL消息,所有收到这条消息的节点都会立即将节点B标记为已下线;
  • PUBLISH消息:当节点接收到一个publish命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条消息的节点都会执行相同的publish命令。

槽(slot)

  Redis集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为16384个可用插槽(slot),数据库中的每个键都属于这16834个插槽中的一个,集群中的每个节点可以处理0个或最多16834个插槽。
  当数据库中的16834个槽都有节点在处理时,集群处于上线状态(ok),相反地,如果数据库中有任何一个槽没有得到处理,集群处于下线状态(fail).
  前面我们搭建的集群极为下线状态,因为集群中的3个节点都没有处理任何槽,我们可以通过cluster info命令查看其信息:

127.0.0.1:6380> cluster info
cluster_state:fail  //集群未上线
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:0
cluster_current_epoch:2
cluster_my_epoch:0
cluster_stats_messages_ping_sent:24
cluster_stats_messages_pong_sent:25
cluster_stats_messages_meet_sent:2
cluster_stats_messages_sent:51
cluster_stats_messages_ping_received:25
cluster_stats_messages_pong_received:26
cluster_stats_messages_received:51

指派槽给不同节点

  既然集群未上线,需要指派所有槽给不同节点,那么我们指派就好了,通过redis-cli -h <IP> -p <Port> cluster addslots {0..5460}命令可以指派相应槽给指定节点:

$ redis-cli -h 127.0.0.1 -p 6380 cluster addslots {0..5460}
OK

# wksky @ localhost in ~ [10:47:57] 
$ redis-cli -h 127.0.0.1 -p 6381 cluster addslots {5461..11111}
OK

# wksky @ localhost in ~ [10:48:29] 
$ redis-cli -h 127.0.0.1 -p 6382 cluster addslots {11112..16383}
OK

  因为16384个槽都分配完毕,所以现在集群处于上线状态:

$ redis-cli -c -p 6380
127.0.0.1:6380> cluster info
cluster_state:ok  //集群上线
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:2
cluster_my_epoch:1
cluster_stats_messages_ping_sent:106
cluster_stats_messages_pong_sent:108
cluster_stats_messages_meet_sent:2
cluster_stats_messages_sent:216
cluster_stats_messages_ping_received:108
cluster_stats_messages_pong_received:108
cluster_stats_messages_received:216
127.0.0.1:6380> cluster nodes
f661beaff3b734caa56c8e92a62c0e4e7f661c2d 127.0.0.1:6381@16381 master - 0 1555130707063 0 connected 5461-11111
d033f0bde8d29441ea01ba275ad93714a80219ba 127.0.0.1:6382@16382 master - 0 1555130708072 2 connected 11112-16383
ab8516a5f78a700fb982b03db954fb0946ee7489 127.0.0.1:6380@16380 myself,master - 0 1555130705000 1 connected 0-5460

节点中的槽信息(可跳过)

  前面说过,不同的节点都保存了一个clusterNode结构,该结构中的slotsnumslot属性记录了节点复制处理哪些槽。slots属性是一个二进制数组,长度2048字节,包含16384个二进制位,将二进制位编号为索引,根据索引上的二进制位的值判断节点是否负责处理槽i,numslot即位处理槽的数量(所有值相加,下图数量为7),如下图:

slots数组

  一个节点除了会将自己负责除了的槽信息记录在2个属性当中,还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽,如下图:
消息发送

在集群中执行命令

  在对数据库中的16384个槽都进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了。
  当客户端向节点发送与数据库键有关的命令时(如set username lucy),接受命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己,如下图:

判断客户端是否需要转向的流程

  比如我们在之前6380、6381、6382三个节点组成的集群中,用客户端连接节点6380,执行下面的命令:

127.0.0.1:6380> set date 2019-4-13
OK

  因为键date所在的槽正是节点6380负责处理的,所以执行命令成功,如果不是呢?比如键username不是当前节点能处理的,它会说该键由14315槽处理,而该槽需要在节点6382才能处理,所以重定向到6382节点进行处理,代码如下:

127.0.0.1:6380> set username lucy
-> Redirected to slot [14315] located at 127.0.0.1:6382
OK
127.0.0.1:6382> 

键所在槽位置计算

  不同键所属槽可以通过它给定的算法(CRC)计算,此处不再介绍。

节点数据库

  集群节点保存键值对以及键值对过期时间的方式和单机Redis服务器保存键值对以及键值对过期时间的方式完全相同,此处不再介绍。
  节点和单机数据库在数据库方面的一个区别是:节点只能使用0号数据库,而单机Redis服务器则没有这一限制。

重新分片

  Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽指派给另一个节点(目标)节点,并且相关槽所属的键值对也会从源节点被移动到目标节点。
  重新分片操作可以在线进行,该操作集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
  举个例子,对之前包含6380、6381、6382三个节点的集群来说,我们可以向这个集群添加一个IP为127.0.0.1,端口号为6390的节点(请先启动该节点):

127.0.0.1:6380> cluster meet 127.0.0.1 6390
OK
127.0.0.1:6380> cluster nodes
f661beaff3b734caa56c8e92a62c0e4e7f661c2d 127.0.0.1:6381@16381 master - 0 1555130814019 0 connected 5461-11111
a6e9d00dc85f274259ed9e21417885d1085f4f33 127.0.0.1:6390@16390 master - 0 1555130813009 3 connected
d033f0bde8d29441ea01ba275ad93714a80219ba 127.0.0.1:6382@16382 master - 0 1555130812000 2 connected 11112-16383
ab8516a5f78a700fb982b03db954fb0946ee7489 127.0.0.1:6380@16380 myself,master - 0 1555130812000 1 connected 0-5460

  从节点信息可以很明显的看到6390节点没有指派任何一个槽,现在我们将6382指派过的槽中的11112槽指派给6390节点:

127.0.0.1:6390> cluster delslots 11112
OK
127.0.0.1:6390> cluster addslots 11112
OK
a6e9d00dc85f274259ed9e21417885d1085f4f33 127.0.0.1:6390@16390 myself,master - 0 1555133779000 3 connected 11112
d033f0bde8d29441ea01ba275ad93714a80219ba 127.0.0.1:6382@16382 master - 0 1555133780877 2 connected 11113-16383
f661beaff3b734caa56c8e92a62c0e4e7f661c2d 127.0.0.1:6381@16381 master - 0 1555133780574 0 connected 5461-11111
ab8516a5f78a700fb982b03db954fb0946ee7489 127.0.0.1:6380@16380 master - 0 1555133780000 1 connected 0-5460

  这样分片十分麻烦,所以一般使用Redis的集群管理软件redis-trib来进行分片,介绍见一篇文章

注意:重新分片过程中可能出现ASK错误,即客户端发送获取键的命令的时候,该键所处的槽正在从一个节点迁移到另一个节点,就会报ASK错误,不过节点会根据这个错误提供的IP和端口号转向目标节点发送ASKING命令,之后在重新发送原本想要执行的命令,所以没啥事。。。

节点复制与故障转移

  Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,从节点用于复制某个主节点(从节点不含槽),并在被复制的主节点下线时,代替下线主节点继续处理命令请求.
  举个例子,对于包含7000、7001、7002、7003、7004、7005的六个主节点集群来说,我们可以将7004和7005设定为节点7000的从节点,如下图:

7004和7005为节点7000的从节点

  设定各个节点状态即它们工作如下图:
各节点状态

  当节点7000进入下线状态,集群经过一段时间后将进行故障转移工作,仍在运行的几个主节点就会将节点7000的两个从节点之一挑选出一个新的作为新的主节点,其接管原来节点7000负责处理的槽,并继续处理客户端发送的命令请求:
7004成为新的主节点

之后各节点状态

  故障转移之后,下线的7000节点重新上线成为节点7004的从节点,如下图:
7000节点重新上线成为节点7004的从节点

之后各节点工作状态

  当然这些工作并没有这么简单就完成,要经过下面3个步骤:
  ①设置从节点;
  ②故障检测;
  ③故障转移。

设置从节点

  通过向一个节点发送命令cluster replicate <node_id>即可让当前节点成为node_id节点的从节点,并开始对node_id节点进行复制:

127.0.0.1:7004> cluster replicate f661beaff3b734caa56c8e92a62c0e4e7f661c2d
OK
127.0.0.1:7005> cluster replicate f661beaff3b734caa56c8e92a62c0e4e7f661c2d
OK

故障检测

  集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定时间内向发送PING的节点返回PONG消息,接收节点就会被发送节点标记为疑似下线
  如果在一个集群里面,半数以上的负责处理槽的主节点都将某个主节点标记为疑似下线,那么这个主节点将被标记为已下线,并向集群广播一条关于该节点的FALL消息。

故障转移

  当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移,执行下面步骤:
1)从下线主节点的所有从节点基于一定规则选中一个执行slaveof no one命令,成为新的主节点;
2)新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己;
3)新的主节点会向集群广播一条PONG消息,让其他节点立即知道当前节点由从节点变为了主节点,并且新主已接管所有旧主负责处理的槽;
4)新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。
  

快速搭建集群

  如果节点多的话手动组建集群很麻烦,所以我们可以一次性指定多个节点为一个集群(前提是所有节点之间未曾握手构成小集群):

redis-cli --cluster create 127.0.0.1:6380 127.0.0.1:6381  127.0.0.1:6382 127.0.0.1:6390 127.0.0.1:6391 127.0.0.1:6392 --cluster-replicas 1
//--cluster-replicas 1意味着我们希望每个创建的主服务器都有一个从服务器

  

>>> 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:6391 to 127.0.0.1:6380
Adding replica 127.0.0.1:6392 to 127.0.0.1:6381
Adding replica 127.0.0.1:6390 to 127.0.0.1:6382
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: b6477a2b03d54a3e5fda45f4939c971724a80346 127.0.0.1:6380
   slots:[0-5460] (5461 slots) master
M: e355801e186e02f9a0b7cbb989914a564c104721 127.0.0.1:6381
   slots:[5461-10922] (5462 slots) master
M: 886791bf33ad1dc49463a992e5ff28305f1b02e5 127.0.0.1:6382
   slots:[10923-16383] (5461 slots) master
S: 129888f320539c541599359765536e85280b96a0 127.0.0.1:6390
   replicates 886791bf33ad1dc49463a992e5ff28305f1b02e5
S: 04b64979a1ae62144bb6f0474a181ec4832e517d 127.0.0.1:6391
   replicates b6477a2b03d54a3e5fda45f4939c971724a80346
S: 6761ca00082ebdf75cfd1101189e2cfcde064755 127.0.0.1:6392
   replicates e355801e186e02f9a0b7cbb989914a564c104721
Can I set the above configuration? (type 'yes' to accept): 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:6380)
M: b6477a2b03d54a3e5fda45f4939c971724a80346 127.0.0.1:6380
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 129888f320539c541599359765536e85280b96a0 127.0.0.1:6390
   slots: (0 slots) slave
   replicates 886791bf33ad1dc49463a992e5ff28305f1b02e5
M: e355801e186e02f9a0b7cbb989914a564c104721 127.0.0.1:6381
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 04b64979a1ae62144bb6f0474a181ec4832e517d 127.0.0.1:6391
   slots: (0 slots) slave
   replicates b6477a2b03d54a3e5fda45f4939c971724a80346
M: 886791bf33ad1dc49463a992e5ff28305f1b02e5 127.0.0.1:6382
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 6761ca00082ebdf75cfd1101189e2cfcde064755 127.0.0.1:6392
   slots: (0 slots) slave
   replicates e355801e186e02f9a0b7cbb989914a564c104721
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

  显然,我们要求的唯一设置是创建一个包含3个主服务器和3个从服务器的集群。
  当你输入完命令后,Redis会给你一份配置清单(将决定哪个服务器是主服务器,哪个服务器是从服务器),之后会提示你是否希望这样配置,输入yes接受建议的配置,执行配置将给定所有节点加入集群,这意味着节点将被引导为彼此通信。最后,如果一切顺利,你会看到这样的消息:

[OK] All 16384 slots covered

  这意味着至少有一个主实例为16384个可用插槽提供服务。

  现在我们来看一下集群的信息:

$ redis-cli -p 6380
127.0.0.1:6380> 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:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1826
cluster_stats_messages_pong_sent:1872
cluster_stats_messages_sent:3698
cluster_stats_messages_ping_received:1867
cluster_stats_messages_pong_received:1826
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:3698

参考资料

《redis设计与实现》(第二版)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • redis集群分为服务端集群和客户端分片,redis3.0以上版本实现了集群机制,即服务端集群,3.0以下使用客户...
    hadoop_null阅读 1,583评论 0 6
  • 1. 集群概述 Redis集群是Redis提供的分布式数据库方案,集群通过分片进行数据共享,提供复制和故障转移功能...
    孤尘F阅读 4,552评论 0 0
  • 《超级个体-伽蓝214》307/365,9.14打卡,明媚的光 【三件事】 1. [ ] 非公医疗项目开始进行50...
    伽蓝214阅读 223评论 0 0
  • 上次见你的时候 墙外的花开得正艳 多日不见 现已凋谢 光秃秃的枝干有些丑
    夸我是狗阅读 131评论 0 0
  • 长平之战后,赵都邯郸被秦所围。平原君出使六国寻求救援。毛遂自荐“臣乃今日请处囊中耳!使遂蚤得处囊中,乃颖脱而出,非...
    南邻先生阅读 354评论 4 4