因为进行Redis集群部署操作的实验需要启停较多的Redis服务,因此,我写了如下两个简单的脚本来辅助启停本机Redis应用。
要使用集群功能,Redis 服务必须要先打开 cluster-enabled 配置选项,修改path/to/redis.conf 文件中的 cluster-enabled 配置项的值为 yes
启动redis实例startRedis.sh
#!/usr/bin/bash
#在本机启动N个redis实例,redis实例的端口从6379开始,每启动一个实例端口递增1
#如:启动两个redis实例,两个实例的端口分别为 6379和6380
#脚本默认使用当前目录下的myRedis.conf文件作为配置文件。此配置文件中必须要有cluster-enabled yes配置项,以开启Redis集群功能
#在当前目录下根据启动Redis的端口创建子目录,每个redis的aof、rdb、config等文件都在相应的子目录下
declare -i port=6378
declare -i n=$1
declare -i i=1
echo "启动redis实例个数为:$n"
test $n -le 0 && n=1 #若启动脚本没有带参数,默认启动1个redis实例
while [ $i -le $n ]
do
port=$((port+1))
echo "开始启动redis实例,监听端口$port"
# 若当前目录下不存在相应端口的子目录,则创建
test -d $port || mkdir $port
# 启动Redis服务
redis-server myRedis.conf --port $port --cluster-config-file nodes-$port.conf --appendfilename appendonly-$port.aof --dbfilename dump-$port.rdb --dir ./$port &
i=$((i+1))
done
echo "所有redis实例启动成功!"
exit 0
停止redis实例stopRedis.sh
#!/usr/bin/bash
#停止本机所有的redis服务
#获取本机所有redis服务的端口
portArr=`ps -ef | grep redis-server | sed '/grep/'d | awk '{print $9}' | awk -F : '{print $2}'`
echo "所有的redis服务端口 $portArr"
for port in $portArr
do
echo "停止$port端口上的redis服务"
redis-cli -p $port shutdown
done
exit 0
启停脚本的使用范例:
//在本机上启动6个Redis服务,端口为6379-6384
[root@localhost]# bash startRedis.sh 6
//停止本机的所有Redis服务
[root@localhost]# bash stopRedis.sh
本文章中使用Redis自带的redis-trib.rb工具来进行Redis集群的部署,redis-trib.rb是用ruby编写的,所有要先安装ruby,然后安装redis gem依赖包。
//centos系统下安装ruby,执行以下命令
//其他安装方式参看官方文档
[root@localhost]# yum install ruby
//安装redis-trib.rb依赖的gem包redis
[root@localhost]# gem install redis
//启动redis服务,创建6个redis服务,端口以6379开始递增
[root@localhost]# bash startRedis.sh 6
//创建部署redis集群
[root@localhost]# /home/ojh/redis/redis-stable/src/redis-trib.rb create --replicas 1 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 MEET ip port命令即可,其中ip和port是原集群中任一节点的ip和端口。原集群中节点接收到meet命令后会通过Gossip协议通知集群中每一个节点,因此新节点仅需meet集群中任一节点即可。
节点添加到集群中后,可以通过CLUSTER REPLICATE nodeId命令将节点设置为节点nodeId的从服务。
下面例子向集群中增加一主一从两个节点:
//启动redis服务,增加到集群中的新节点
[root@localhost]# mkdir 6385
[root@localhost]# redis-server myRedis.conf --port 6385 --cluster-config-file nodes-6385.conf --appendfilename appendonly-6385.aof --dbfilename dump-6385.rdb --dir ./6385 &
//启动后看新节点的nodes config文件可以看到新节点还没加入到集群中
[root@localhost redisCuster]# cat 6385/nodes-6385.conf
cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
//向新节点发送CLUSTER MEET ip port命令
[root@localhost]# redis-cli -p 6385
127.0.0.1:6385> CLUSTER MEET 127.0.0.1 6383
//meet后再看6385的config文件,可以看到6385已经成功添加到集群上了
[root@localhost redisCuster]# cat 6385/nodes-6385.conf
89887c506c252150edf732aca16593fc75e8e72e 127.0.0.1:6383 slave ef399174965146030fa40f3fdaaa832e17519b32 0 1471311539434 2 connected
cc51b29ff110c1be59570ba4710f94a03de56a36 127.0.0.1:6382 slave 5009b4bfd37a4a957fd1780a6ae463a2cd014d82 0 1471311539329 1 connected
5009b4bfd37a4a957fd1780a6ae463a2cd014d82 127.0.0.1:6379 master - 0 1471311539434 1 connected 0-5460
95e173133ce6df95948ef787546fd7a3b7853f0b 127.0.0.1:6384 slave c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 0 1471311539328 3 connected
ef399174965146030fa40f3fdaaa832e17519b32 127.0.0.1:6380 master - 0 1471311539434 2 connected 5461-10922
cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 127.0.0.1:6385 myself,master - 0 0 0 connected
c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 127.0.0.1:6381 master - 0 1471311539329 3 connected 10923-16383
vars currentEpoch 6 lastVoteEpoch 0
//启动redis服务,将此服务设置为6385端口上Redis服务的从服务
[root@localhost]# mkdir 6386
[root@localhost]# redis-server myRedis.conf --port 6386 --cluster-config-file nodes-6386.conf --appendfilename appendonly-6386.aof --dbfilename dump-6386.rdb --dir ./6386 &
//启动后看新节点的nodes config文件可以看到新节点还没加入到集群中
[root@localhost]# cat 6386/nodes-6386.conf
8e4ae1fbda3fc2e30d1b97d3e46711192d21970d :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
//向集群中增加节点只要向新节点发送CLUSTER MEET ip port命令
[root@localhost]# redis-cli -p 6386
127.0.0.1:6386> CLUSTER MEET 127.0.0.1 6383
//此时查看nodes.config文件会发现6386已经以主服务形式加入到集群中
[root@localhost]# cat 6386/nodes-6386.conf
89887c506c252150edf732aca16593fc75e8e72e 127.0.0.1:6383 slave ef399174965146030fa40f3fdaaa832e17519b32 0 1471313606925 2 connected
8e4ae1fbda3fc2e30d1b97d3e46711192d21970d 127.0.0.1:6386 myself,master - 0 0 7 connected
5009b4bfd37a4a957fd1780a6ae463a2cd014d82 127.0.0.1:6379 master - 0 1471313606816 1 connected 0-5460
ef399174965146030fa40f3fdaaa832e17519b32 127.0.0.1:6380 master - 0 1471313606716 2 connected 5461-10922
cc51b29ff110c1be59570ba4710f94a03de56a36 127.0.0.1:6382 slave 5009b4bfd37a4a957fd1780a6ae463a2cd014d82 0 1471313606842 1 connected
95e173133ce6df95948ef787546fd7a3b7853f0b 127.0.0.1:6384 slave c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 0 1471313606842 3 connected
c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 127.0.0.1:6381 master - 0 1471313606716 3 connected 10923-16383
cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 127.0.0.1:6385 master - 0 1471313606716 0 connected
vars currentEpoch 7 lastVoteEpoch 0
//将6386设置为6385的从服务
//发送CLUSTER REPLICATE nodeId命令将此redis节点设置成nodeId节点的从redis服务
[root@localhost]# redis-cli -p 6386
127.0.0.1:6386> CLUSTER REPLICATE cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54
//此时再看nodes.config文件,会发现6386已经是6385的从服务了
[root@localhost redisCuster]# cat 6386/nodes-6386.conf
89887c506c252150edf732aca16593fc75e8e72e 127.0.0.1:6383 slave ef399174965146030fa40f3fdaaa832e17519b32 0 1471313751464 2 connected
8e4ae1fbda3fc2e30d1b97d3e46711192d21970d 127.0.0.1:6386 myself,slave cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 0 0 7 connected
5009b4bfd37a4a957fd1780a6ae463a2cd014d82 127.0.0.1:6379 master - 0 1471313749432 1 connected 0-5460
ef399174965146030fa40f3fdaaa832e17519b32 127.0.0.1:6380 master - 0 1471313749838 2 connected 5461-10922
cc51b29ff110c1be59570ba4710f94a03de56a36 127.0.0.1:6382 slave 5009b4bfd37a4a957fd1780a6ae463a2cd014d82 0 1471313748414 1 connected
95e173133ce6df95948ef787546fd7a3b7853f0b 127.0.0.1:6384 slave c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 0 1471313752511 3 connected
c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 127.0.0.1:6381 master - 0 1471313753543 3 connected 10923-16383
cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 127.0.0.1:6385 master - 0 1471313750241 0 connected
vars currentEpoch 7 lastVoteEpoch 0
对集群中哈希槽进行重新分配,根据哈希槽的状态可以分成两种情况:
1、待分配的哈希槽还未分配到某个节点中,此时简单使用CLUSTER ADDSLOTS slotsNum命令即可将slotsNum号哈希槽分配到当前节点中。
2、待分配的哈希槽已经分配给某个节点,此时可以使用redis-trib.rb reshard命令对哈希槽进行自动重新分配;或者根据下文所说的redis-trib.rb reshard的操作过程进行手工重新分配。
//执行redis-trib.rb reshard ip:port命令对集群重新分配哈希槽,根据提示输入要移动的哈希槽数量,接收哈希槽的节点,和移除的节点等信息即可。【ip、port为集群中任一节点的ip和端口即可】
[root@localhost]# /home/ojh/redis/redis-stable/src/redis-trib.rb reshard 127.0.0.1:6379
//然后使用CLUSTER SLOTS命令可以查看集群哈希槽的分配情况
[root@localhost]# redis-cli -p 6386
127.0.0.1:6386> CLUSTER SLOTS
//可以通过向redis服务发送CLUSTER ADDSLOTS slotsNum 命令向某个节点分配哈希槽(但是此种方式只能分配未被分配过的哈希槽,若此哈希槽已经分配给某个节点,将会报出(error) ERR Slot slotsNum is already busy错误信息)【其中slotsNum为哈希槽编号,取值为:0-16383】
//若要把已经分配到某个节点的某哈希槽重新分配到另一个节点可以使用命令CLUSTER SETSLOT实现,但是此命令不会将原来槽中的键迁移到新节点中,因此会造成原有的键丢失
redis-trib.rb reshard对哈希槽进行重新分配的过程:
假设将0号哈希槽从id为nodeIdA的节点迁移到nodeIdB的节点中。【其中nodeIdA、nodeIdB为节点的id】
1)在节点nodeIdB中执行CLUSTER SETSLOT 0 IMPORTING nodeIdA;【迁入节点】
2)在节点nodeIdA中执行CLUSTER SETSLOT 0 MIGRATING nodeIdB;【迁出节点】
3)在节点nodeIdA中执行CLUSTER COUNTKEYSINSLOT 0,返回0号哈希槽的键(key)数量,假设为n
4)在节点nodeIdA中执行CLUSTER GETKEYSINSLOT 0 n ,返回0号哈希槽的所有键【n为上一步得到的键数量】
5)在节点nodeIdA中对上一步的每一个键执行MIGRATE命令,将所有键都迁移到nodeIdB节点【MIGRATE命令格式:MIGRATE targetIp targetPort keyName dbNum milliseconds [COPY] [REPLACE]】
6)在节点nodeIdA中执行CLUSTER SETSLOT 0 NODE nodeIdB命令将0号哈希槽迁移到nodeIdB节点,【执行完这条指令后客户端要操作0号哈希槽的键就会向nodeIdB节点发送请求】
其中第1、2两步可以保证在迁移过程中不会出现某些键临时丢失的问题:当客户端向 nodeIdA请求0号哈希槽中的键时,如果键存在(即尚未被迁移),则正常处理,如果不存在,则返回一个ASK跳转请求,告诉客户端这个键在nodeIdB节点里。客户端接收到 ASK跳转请求后,首先向nodeIdB发送ASKING命令,然后再重新发送之前的命令。相反,当客户端向nodeIdB请求0好哈希槽中的键时,如果前面执行了ASKING 命令,则返回键值内容,否则返回 MOVED重定向请求,将请求重定向到节点nodeIdA中。
当客户端向集群中的任意一个节点发送命令后,该节点会判断相应的键是否在当前节点中,如果键在该节点中,则会像单机实例一样正常处理该命令;如果键不在该节点中,就会返回一个MOVED重定向请求,告诉客户端这个键目前由哪个节点负责,然后客户端再将同样的请求向目标节点重新发送一次以获得结果。
如:
[ojh@localhost ~]$ redis-cli -p 6379
127.0.0.1:6379> set a test
(error) MOVED 15495 127.0.0.1:6381
//错误返回值,指出键对应的哈希槽的编号(15495),以及该编号哈希槽所在的节点ip:端口(127.0.0.1:6381)
//收到此返回值后再向127.0.0.1:6381上的redis节点发送set a test才会设置成功
[ojh@localhost ~]$ redis-cli -p 6381
127.0.0.1:6381> set a test
OK
在集群中,每个节点每秒钟会从集群中随机选择5个节点,然后在此5个节点中选择最久没有响应的节点发送PING命令,如果目标节点超时没有回复,则发送PING命令的节点认为目标节点疑似下线(PFAIL)【类似于哨兵的主观下线】。若一个节点认为某一节点疑似下线,则会向集群所有节点广播该消息,其他所有的节点收到消息后会记录下来,当有某一节点收集到半数以上节点认为此目标节点疑似下线,就将此目标节点标记为下线,然后向集群中所有其他节点广播此条消息。
若下线的目标节点有从属节点,则在从属节点中通过Raft算法选举出一个节点作为主服务,替代原来下线的目标节点,负责下线节点的哈希槽的读写操作。
若下线的目标节点没有从属节点,且其负责1个或以上哈希槽的读写操作,则整个集群默认下线,不再提供服务。【可以通过/path/to/redis.config配置文件中修改cluster-require-full-coverage配置向为no使集群在这种情况下继续工作】
选举作为主数据库的Raft算法和哨兵选举领头哨兵的步骤大致相同:
1)从数据库(假设A)发现其主数据库下线,则向集群中所有节点发送请求,要求对方选举自己为主数据库;
2)若接收请求的节点没有选举过其他候选数据库,则同意将此数据库A选举成主数据库。
3)若A发现有超过集群节点总数一半的节点同意选择自己为主数据库,则A成功选为主数据库,然后通过slaveof no one命令将自己设置成主数据库
4)当有多个从数据库同时参选,则没有任何从数据库成功当选,每个参选节点等待一个随机的时间从新参选,开始新的一轮选举,直到选举成功。
下面我们来模拟下集群中节点出现故障下线,来看看集群故障恢复和故障节点重启后集群的变化:
[root@localhost redisCuster]$ cat 6379/nodes-6379.conf
cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 127.0.0.1:6385 master - 0 1471316085672 8 connected 0-1332 5461-6794 10923-12255
5009b4bfd37a4a957fd1780a6ae463a2cd014d82 127.0.0.1:6379 myself,master - 0 0 1 connected 1333-5460
ef399174965146030fa40f3fdaaa832e17519b32 127.0.0.1:6380 master - 0 1471316087685 2 connected 6795-10922
8e4ae1fbda3fc2e30d1b97d3e46711192d21970d 127.0.0.1:6386 slave cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 0 1471316085672 8 connected
c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 127.0.0.1:6381 master - 0 1471316088687 3 connected 12256-16383
95e173133ce6df95948ef787546fd7a3b7853f0b 127.0.0.1:6384 slave c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 0 1471316088186 6 connected
89887c506c252150edf732aca16593fc75e8e72e 127.0.0.1:6383 slave ef399174965146030fa40f3fdaaa832e17519b32 0 1471316086677 5 connected
cc51b29ff110c1be59570ba4710f94a03de56a36 127.0.0.1:6382 slave 5009b4bfd37a4a957fd1780a6ae463a2cd014d82 0 1471316089690 4 connected
vars currentEpoch 8 lastVoteEpoch 0
//从上面输出信息可以看到6380是主数据库,6383是6380的从数据库
//然后我们吧6380停掉,模拟此节点出现故障
[root@localhost redisCuster]$ redis-cli -p 6380 shutdown
//稍等一下,再看就发现集群中6380已经下线,6383提升为主数据库,至此,集群的自动故障恢复过程完成
[root@localhost redisCuster]$ cat 6379/nodes-6379.conf
cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 127.0.0.1:6385 master - 0 1471338877199 8 connected 0-1332 5461-6794 10923-12255
5009b4bfd37a4a957fd1780a6ae463a2cd014d82 127.0.0.1:6379 myself,master - 0 0 1 connected 1333-5460
ef399174965146030fa40f3fdaaa832e17519b32 127.0.0.1:6380 master,fail - 1471338859593 1471338857504 2 disconnected
8e4ae1fbda3fc2e30d1b97d3e46711192d21970d 127.0.0.1:6386 slave cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 0 1471338873491 8 connected
c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 127.0.0.1:6381 master - 0 1471338878219 3 connected 12256-16383
95e173133ce6df95948ef787546fd7a3b7853f0b 127.0.0.1:6384 slave c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 0 1471338875155 6 connected
89887c506c252150edf732aca16593fc75e8e72e 127.0.0.1:6383 master - 0 1471338876206 9 connected 6795-10922
cc51b29ff110c1be59570ba4710f94a03de56a36 127.0.0.1:6382 slave 5009b4bfd37a4a957fd1780a6ae463a2cd014d82 0 1471338873082 4 connected
vars currentEpoch 9 lastVoteEpoch 9
//然后我们继续启动6380服务
[ojh@localhost redisCuster]$ redis-server myRedis.conf --port 6380 --cluster-config-file nodes-6380.conf --appendfilename appendonly-6380.aof --dbfilename dump-6380.rdb --dir ./6380 &
//此时再看集群配置发现6380已经是6383的从数据库了
[root@localhost redisCuster]# cat 6379/nodes-6379.conf
cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 127.0.0.1:6385 master - 0 1471339248224 8 connected 0-1332 5461-6794 10923-12255
5009b4bfd37a4a957fd1780a6ae463a2cd014d82 127.0.0.1:6379 myself,master - 0 0 1 connected 1333-5460
ef399174965146030fa40f3fdaaa832e17519b32 127.0.0.1:6380 slave 89887c506c252150edf732aca16593fc75e8e72e 0 1471339248634 9 connected
8e4ae1fbda3fc2e30d1b97d3e46711192d21970d 127.0.0.1:6386 slave cb3e00c246f4e7eea69eafdf2bf73851c0bc2e54 0 1471339243971 8 connected
c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 127.0.0.1:6381 master - 0 1471339242946 3 connected 12256-16383
95e173133ce6df95948ef787546fd7a3b7853f0b 127.0.0.1:6384 slave c035d4bf06fb1b0a29fb45fa1ff773ed6e4bbb8e 0 1471339246356 6 connected
89887c506c252150edf732aca16593fc75e8e72e 127.0.0.1:6383 master - 0 1471339246153 9 connected 6795-10922
cc51b29ff110c1be59570ba4710f94a03de56a36 127.0.0.1:6382 slave 5009b4bfd37a4a957fd1780a6ae463a2cd014d82 0 1471339247174 4 connected
vars currentEpoch 9 lastVoteEpoch 9