Redis集群实现

简介

前面两篇文章介绍到了Redis的主从节点Redies的数据分片,但在实际环境中,我们可能需要同时满足这两个要求,即既能实现主从结构(master/slave)解决主节点(master)出现故障,从节点(slave)能够提升为主节点,不影响整个程序的运行;而且又能够满足当业务压力太大,单个master节点的写入无法满足要求时,能够实现数据分片的功能。而Redis 3.0.2提供了Redis集群功能,能够很好的解决这个问题。

Redis Cluster提供了以下功能:

 

  • 自动将数据分存储到不同的节点
  • 当某些节点(少数)出现故障或者无法与其他节点进行通讯时,整个集群依旧能够正常的提供功能

Tcp Port

每个Redis Cluster中的节点需要建立两个TCP 连接,一个端口用于服务客户端(Command port),例如6379;另一个端口是数据端口(Cluster bus port),在前一个端口上加10000,如16379,该端口主要用于集群总线,这是一个用binary协议进行集群节点间通讯的通道。集群总线用于节点的故障决策,配置更新,故障转移,数据交互等等。客户端不要尝试去和集群总线的端口进行通讯,并且保证这两个端口在防火墙中是开启状态,否则Reis 集群不能正常的工作。Command port 和Cluser bus port总是相差10000。

Redis集群数据分区

Redis Cluster没用用一致性哈希算法,而是一种不同的分区形式,每一个Key概念上都属于我们称为hash slot(hash槽)的一部分。在Redis Cluster中有16384个hash slot,通过key的CRC16的值取模16384来计算每个key所属的hash slot。
在Redis Cluster中每个节点负责hash槽的部分子集,比如下面一个集群节点的hash slots分布:
  • NodeA 包含 0 - 5000的hash slots.
  • NodeB 包含 5001 - 10000的hash slots.
  • NodeC 包含 10001 - 16383的hash slots
通过这种机制就很容易在集群中添加或删除节点了。比如如果要加一个新的节点NodeD,只需要在A, B, C中移部分hash slots给D; 同样如果删除A,只需要把A上的hash slots分配给B, C,当A空了的时候,就可以把A从集群中移除掉了。当把hash slots 从一个节点移到另一个节点时不用停止操作,故增加节点,删除节点或者改变节点负责的hash slots的比例都不要停止Redis集群。
Redis Cluster支持属于同一个hash slot的多个主键联合操作,用户可以通过hash tags强制多个key属于同一个hash slot。hash tags在Redis Clustre Specification中有解释。

Redis集群主从模式

为了实现Redis集群当少数master节点出现故障或者无法与多数节点通讯时依旧能够正常提供服务,Redis集群用了master/slave模式,这样每个hash slot都有1(master节点本身)到N个冗余(N-1为slave节点的个数)。在上个例子中,如果为A,B,C三个节点各建立一个slave节点A1,B1,C1,这样如果当B节点出现故障的话,集群将会将B1节点从slave状态提升为master状态,就不会影响正常的操作了。当然,如果B,B1同时出现异常的话,那么集群就无法正常提供服务了。

Redis集群一致性保证

Redis集群无法提供强一致性,在实际的环境中这意味着在某一情形下,Redis集群有可能丢失掉部分数据,尽管系统已经发送确认给客户端。 丢失数据的第一个原因是主从同步用的是异步模式,在下述场景下就有可能丢失数据:
  • 客户端发送写请求给master B
  • B 返回ok给客户端
  • B 将写的操作传递给Slave B1, B2, B3
在上述场景中,B并没有等待B1, B2, B3的确认就直接返回给客户端OK,这样如果在B将写请求发送给slave的时候挂掉了,而那些没有收到这个写请求的slave节点被提升为Master节点,其他的slave节点再从这个新的master节点同步数据,那这写操作的数据就会永远的丢失掉。
这与大多数数据库配置将数据每秒刷新到磁盘上很类似。所以你就能够明白这个场景,因为传统的数据库系统没有涉及到分布式系统的。你也可以通过强制在数据刷新到磁盘后在返回ok给客户端来改善一致性,但这通常会导致低性能。这等价于Redis Cluster的同步复制功能。
通常我们要在性能和一致性之间进行权衡。Redis Cluster支持同步写,通过WAIT命令,这将会很少丢失数据,但是注意:Redis集群没有实现强一致性,即在这种情况下,还是有可能会丢失数据的,具体场景就就不再列举了,可参考Redis Cluster自己的介绍:Redis Cluser tutorial

Redis集群的配置参数

在部署一个Redis集群系统之前,我们先来介绍下Redis Cluster的一些参数,在redis.conf文件中。
cluster-enabled(yes/no):yes表示将以Redis Cluster模式启动Redis实例,否则将以普通的Redis实例启动。
cluster-confile-file(filename):该文件名虽然由用户制定,但是该文件的创建与更新则是由Redis cluster自己完成,该文件记录当前节点以及整个集群节点等信息。
cluster-node-timeout<milliseconds>:Redis 集群节点不可用的最大时间。如果一个master节点超过指定的最大时间内不可达,则会被它的slaves节点认为是出现故障的,而且如果该节点在超出指定最大时间内无法与其他master节点建立联系,则将停止接受查询。
cluster-slave-validity-factor(factor):
cluster-migration-barrier(count):
cluster-require-full-coverage(yes/no):

创建Redis集群

上面介绍了那么多,现在我们来搭建一套Redis集群环境,该Redis集群由3个master节点和3个slave节点组成。机器有限,就在一台机器上部署,节点信息如下:
master-6379  127.0.0.1  6379
master-6380  127.0.0.1  6380
master-6381   127.0.0.1  6381
slave-6382     127.0.0.1  6382
slave-6383     127.0.0.1  6383
slave-6384     127.0.0.1  6384
  
1. 在官网下载Redis 3.0.2版本,解压到指定磁盘上,拷贝6份,重命名。如下图:
 

2. 修改每个redis.conf文件,增加一下内容:
 
<pre name="code" class="plain"><span style="font-size:14px;">#改为对应的端口
port 6379 
#启动Redis cluster模式
cluster-enabled yes
#根据端口不同改为不同的文件
cluster-config-file nodes-6379.conf
cluster-node-timeout 5000</span>
 
 
3. 依次启动Redis实例
 
以启动6379为例:

启动好之后该6个节点还并没有组成一个集群,各自之间还属于无法互知的状态,可以用Redis-cli连接到6379这个节点然后用命令cluster-nodes 或cluster-info来查看集群的信息,如下所示:
<span style="font-size:14px;">[root@localhost src]# redis-cli -c -p 6379
127.0.0.1:6379> cluster info
cluster_state:fail
cluster_slots_assigned:1
cluster_slots_ok:1
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:1
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
127.0.0.1:6379> cluster nodes
95d1c095746190a47f1b9acd742e1d209d1ed5cc :6379 myself,master - 0 0 0 connected 1797
127.0.0.1:6379> </span>
 
4. 连接Redis cluster各节点
 
可以看到,当前集群状态是fail状态,而集群中只有一个节点,也就是自己。现在通过cluster meet ip port的方式将各个节点联系起来。
 
127.0.0.1:6379> cluster meet 127.0.0.1 6380
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6381
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6382
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6383
OK
127.0.0.1:6379> cluster meet 127.0.0.1 6384
OK
127.0.0.1:6379> cluster nodes
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 master - 0 1434016316258 5 connected
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 master - 0 1434016314239 4 connected
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 myself,master - 0 0 1 connected
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 master - 0 1434016317263 0 connected
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434016318272 3 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434016319283 2 connected
127.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
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_sent:43
cluster_stats_messages_received:43
127.0.0.1:6379>

这时各个节点之间已经相互加入集群了,但是集群的状态还是fail,为什么呢?在官方文档有关于集群状态Fail的原因解释
 
The FAIL state for the cluster happens in two cases.
1) If at least one hash slot is not served as the node serving it currently is in FAIL state.
2) If we are not able to reach the majority of masters (that is, if the majorify of masters are simply in PFAIL state, it is enough for the node to enter FAIL mode).
 
第二条肯定不是,第一条表示如果存在一个hash slot没有分配给节点,集群将会处于FAIL状态,何止有一个hash slot没有被服务,压根儿就没有Redis节点为任何hash slot服务。众所周知,Redis Cluster通过hash slot将数据根据主键来分区,所以一条key-value数据会根据算法自动映射到一个hash slot,但是一个hash slot存储在哪个Redis节点上并不是自动映射的,是需要集群管理者自行分配的。那么我们需要为多少个hash slot分配Redis节点呢?根据源码可知是16384个,即我们要将16384个hash slot分配到集群内的三个节点上。Redis中用于分配hash slot的命令有很多,其中包括cluster addslots、cluster delslots和cluster setslot。鉴于我们现在是集群的初始化阶段,所以我们可以选择cluster addslots来分配hash slot,该命令的语法为cluster addslots slot1 [slot2] ... [slotN]。但是那么多hash slot,我们不可能每个都一个个的增加,我们还有另外一种方法来分配hash slot,那就是通过集群配置文件,也就是我们在第三步中配置的cluster-config-file参数,来完成hash slot的分段配置。
 
5. 为节点分片
 
ndoes-6379.conf文件中,在包含myself即表示当前一行的conneted后追加 0-5000,表示分配0-5000的hash slots给节点6379
 
<span style="font-size:14px;">6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 master - 0 1434016314239 5 connected
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 master - 0 1434016314239 4 connected
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 myself,master - 0 0 1 connected 0-5000
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 master - 0 1434016310197 0 connected
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434016312221 3 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434016315247 2 connected
vars currentEpoch 5 lastVoteEpoch 0</span>

同样的在nodes-6380.conf中加入5001-1000
 
<span style="font-size:14px;">038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 master - 0 1434016310197 1 connected
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434016315247 3 connected
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 master - 0 1434016314849 5 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434016317265 2 connected
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 master - 0 1434016316255 4 connected
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 myself,master - 0 0 0 connected 5001-10000
vars currentEpoch 5 lastVoteEpoch 0</span>
 
在nodes-6381.conf中加入10001-16383
 
<span style="font-size:14px;">d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 master - 0 1434016316456 0 connected
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 master - 0 1434016318480 5 connected
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 master - 0 1434016317470 4 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 myself,master - 0 0 2 connected 100001-16383
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434016314432 3 connected
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 master - 0 1434016315444 1 connected
vars currentEpoch 5 lastVoteEpoch 0</span>
配置好之后我们再重新启动6379,6380,6381的Redis实例,然后在通过cluster info 和cluster nodes查看当前集群的状态
 
<span style="font-size:14px;">127.0.0.1:6379> cluster nodes
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 master - 0 1434016773334 5 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434016774850 2 connected
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 master - 0 1434016772829 4 connected
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 myself,master - 0 0 1 connected 0-5000
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 master - 0 1434016770791 0 connected 5001-10000
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434016773838 3 connected
127.0.0.1:6379> cluster nodes
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 master - 0 1434016805423 5 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434016806431 2 connected 10001-16383
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 master - 0 1434016804413 4 connected
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 myself,master - 0 0 1 connected 0-5000
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 master - 0 1434016804919 0 connected 5001-10000
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434016803403 3 connected
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:1
cluster_stats_messages_sent:267
cluster_stats_messages_received:186
127.0.0.1:6379></span>
 

可以看到,集群状态已经变成OK了。
 
7. 将节点改成从节点
 
查看节点信息,看到6382, 6383,6384还是主节点,现在我们要将这些节点改变6379,6380,6381的从节点。可通过命令cluster-replicate <nodeid>完成,该命令会将当前节点设置为指定节点的从节点,如下:
 
[root@localhost src]# redis-cli -c -p 6382
127.0.0.1:6382> cluster replicate 038d714d39f347019e04968aed0c532fd1e90709
OK
127.0.0.1:6382> cluster info
[root@localhost src]# redis-cli -c -p 6383
127.0.0.1:6383> cluster replicate d1da66d717dffa51655d2092b8c8481589a6cd35
OK
127.0.0.1:6383> 
[root@localhost src]# redis-cli -c -p 6385
Could not connect to Redis at 127.0.0.1:6385: Connection refused
not connected> cluster replicate 038d714d39f347019e04968aed0c532fd1e90709
[root@localhost src]# redis-cli -c -p 6384
127.0.0.1:6384> cluster replicate 412fa99dfed4bbc411d2dfff67709f45dc420ffd
OK
127.0.0.1:6384> cluster nodes
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 master - 0 1434017653767 0 connected 5001-10000
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 myself,slave 412fa99dfed4bbc411d2dfff67709f45dc420ffd 0 0 5 connected
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 slave 038d714d39f347019e04968aed0c532fd1e90709 0 1434017657803 3 connected
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 slave d1da66d717dffa51655d2092b8c8481589a6cd35 0 1434017660829 4 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434017658810 2 connected 10001-16383
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 master - 0 1434017659820 1 connected 0-5000
127.0.0.1:6384>
 
通过cluster nodes可看到 6382,6383,6384已经成为6379,6380,6381的从节点了。
 
8. 操作数据
 
通过redis-cli连接到6380,然后设置数据:
 
[root@localhost src]# redis-cli -c -p 6380
127.0.0.1:6380> set soo far
-> Redirected to slot [1797] located at 127.0.0.1:6379
OK
127.0.0.1:6379> get soo 
"far"
127.0.0.1:6379>
可以看到,虽然是在6380上设置数据,但是由key获取的hash值为1797,是属于0-5000的范围内,故会存入到节点6379上。
 
9. 主从切换
 
当master节点挂掉之后,slave节点会被提升为master节点,如我们关掉6379:
 
<span style="font-size:14px;">127.0.0.1:6380> cluster nodes
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 master,fail - 1434018116151 1434018115242 1 disconnected
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 myself,master - 0 0 0 connected 5001-10000
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 slave 412fa99dfed4bbc411d2dfff67709f45dc420ffd 0 1434018133690 5 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434018134698 2 connected 10001-16383
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434018131664 6 connected 0-5000
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 slave d1da66d717dffa51655d2092b8c8481589a6cd35 0 1434018130653 4 connected
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:0
cluster_stats_messages_sent:3355
cluster_stats_messages_received:2820
127.0.0.1:6380></span>
 
可以看到,6379挂掉之后,它的从节点6382取代它成为了master节点。然后我们再把6379启动,它将成为6382的从节点。
 
>127.0.0.1:6380> cluster nodes
038d714d39f347019e04968aed0c532fd1e90709 127.0.0.1:6379 slave 5a8094389c38eafe30b7af1ae9da2860eed631f7 0 1434018233045 6 connected
d1da66d717dffa51655d2092b8c8481589a6cd35 127.0.0.1:6380 myself,master - 0 0 0 connected 5001-10000
6105b6bc0fd9fae1e995421314f0734c638017f7 127.0.0.1:6384 slave 412fa99dfed4bbc411d2dfff67709f45dc420ffd 0 1434018235068 5 connected
412fa99dfed4bbc411d2dfff67709f45dc420ffd 127.0.0.1:6381 master - 0 1434018232035 2 connected 10001-16383
5a8094389c38eafe30b7af1ae9da2860eed631f7 127.0.0.1:6382 master - 0 1434018230008 6 connected 0-5000
2c8652a57b62c8120c2d5c5dbd0f005869904ec7 127.0.0.1:6383 slave d1da66d717dffa51655d2092b8c8481589a6cd35 0 1434018234056 4 connected
 
10. Redis Cluster的命令
 
集群
  • CLUSTER INFO 打印集群的信息
  • CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。
节点
  • CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
  • CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。
  • CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。
  • CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。
槽(slot)
  • CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。
  • CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。
  • CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
  • CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
  • CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。
  • CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。
  • CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。

  • CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。
  • CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。
  • CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。

猜你喜欢

转载自jaesonchen.iteye.com/blog/2345463
今日推荐