一、redis集群介绍
某业务Redis Cluster中一个节点的负载较高,要将其负责的部分slot迁移到另一个节点。查看集群状态时,出现了flags字段为noaddr的标记。
192.168.1.4:2008> cluster nodes
78641a9cf0098e1c5c07821266dcd1feae21bcd3 :0 slave,noaddr - 1530942381678 1530942361875 0 disconnected
2122990aa0f8b5fae63b36d1a598e5194c72c0d0 :0 master,noaddr - 1530943527689 1530943524090 0 disconnected
...
cluster nodes显示的每一行信息,由下面的字段组成。
<id> <ip:port> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>
1.1 每个字段的含义如下:
1. id: 节点ID,一个40字节的随机字符串,节点创建时生成,且不会变化(除非使用CLUSTER RESET HARD命令)。
2. ip:port: 客户端访问的地址。
3. flags: 逗号分隔的标记位,可能值有:myself, master, slave, fail?, fail, handshake, noaddr, noflags。
4. master: 若是已知master节点的slave,这里出现的是master的节点ID,否则是"-"。
5. ping-sent: 最近一次发送ping的unix毫秒时间戳,0代表没有发送过。
6. pong-recv: 最近一次收到pong的unix毫秒时间戳。
7. config-epoch: 该节点或其master节点的epoch值。每次故障转移都会生成一个新的,唯一的,递增的epoch值。若多个节点竞争相同的slot,epoch值大的获胜。
8. link-state: 节点和集群总线间的连接状态,可以是connected或disconnected。
9. slot: 该节点负责的slot。
1.2 flags字段各标记含义如下:
myself: 当前连接的节点。
master: 节点是master。
slave: 节点是slave。
fail?: 节点处于pfail状态,当前节点无法和其联系,但其它节点可以。
fail: 节点处于fail状态,大多数节点都无法和其联系,将其由pfail升级到fail状态。
handshake: 还没完全加入集群,正在握手阶段。
noaddr: 不知道节点地址。
noflags: 没有任何标记。
二 问题描述
查看集群状态,发现其中两个从节点异常(是fail状态)
在问题节点上查看节点状态,发现它已脱离集群,且id都已发生了变化:
即:
id已经从 76ba12f992228d906c368e0a5978013a210e9cd4 变成 af40bb4ce7a4d623a812b716246c30df49b9db08
/*若id没发生变化,直接重启下该从节点就能解决*/
三 解决办法
3.1 将该从节点剔出集群
#在集群每个正常节点上执行cluster forget 故障从节点id,示例:
echo 'cluster forget f86464011d9f8ec605857255c0b67cff1e794c19' | /usr/local/bin/redis-cli -p 6384 -a "密码"
echo 'cluster nodes' | /usr/local/bin/redis-cli -p 6384 -a "密码"
这种方式可能是以前版本的处理方法,我是5.0版本,总是不成功,使用以下方式forget的
3.2 重新将该节点加入集群
3.2.1 握手
在集群内任意节点上执行cluster meet命令加入新节点,握手状态会通过信息在集群内传播,这样其他节点会自动发现新节点并发起握手流程。
echo 'cluster meet 192.168.109.132 6383' | /usr/local/bin/redis-cli -p 6384 -a "密码"
3.2.2 配置主从关系
#在该从节上执行cluster replicate 主节点id
[root@centos7-mod ~]# echo 'cluster replicate 2cb35944b4492748a8c739fab63a0e90a56e414a' | /usr/local/bin/redis-cli -p 6383 -a "密码"
Warning: Using a password with '-a' option on the command line interface may not be safe.
OK
3.2.3 检查集群状态
[root@centos7-mod ~]# echo 'cluster nodes' | /usr/local/bin/redis-cli -p 6384 -a "密码"
Warning: Using a password with '-a' option on the command line interface may not be safe.
38287a7e715c358b5537a369646e9698a7583459 192.168.109.132:6383@16383 slave 2cb35944b4492748a8c739fab63a0e90a56e414a 0 1615233239757 8 connected
2cb35944b4492748a8c739fab63a0e90a56e414a 192.168.109.133:6383@16383 master - 0 1615233239000 8 connected 0-5461
0e8fa73711c300f2da6f92df49b215d270021b14 192.168.109.134:6383@16383 slave 00dd345835790608dc062dd4742d853138f06b97 0 1615233241763 9 connected
83780e418e90de6067a196d88a54fd2cfe719f86 192.168.109.134:6384@16384 slave 6a50a9c515acf3490e5a5256250d857d0812bc6a 0 1615233240760 17 connected
00dd345835790608dc062dd4742d853138f06b97 192.168.109.133:6384@16384 master - 0 1615233241000 9 connected 5462-10923
6a50a9c515acf3490e5a5256250d857d0812bc6a 192.168.109.132:6384@16384 myself,master - 0 1615233242000 17 connected 10924-16383
四 redis 集群 noaddr error 恢复脚本
本脚本修复nodes.conf文件丢失而导致的noaddr error,修复此类问题有两种解决办法:
1. 直接修复坏节点的nodes.conf文件,这里不赘述。
2. 通过下面脚本在任意节点上执行,目前不支持所有master都坏的情况。
prepare:将所有的ip用逗号分隔,存入环境变量$ip_group.
执行下列脚本,将password替换:
#!/bin/bash
ip_group=`echo $ip_group|tr ',' ' '`
password=$1
echo ">> get cluster info..."
for ip in $ip_group
do
num=`echo cluster nodes | $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password | wc -l`
if (( $num>1 ));
then
cmd_str="echo cluster nodes| $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password"
bad_nodes=`echo cluster nodes| $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password|awk '/:0/'|awk '{print $1}'`
echo "bad_nodes:"$bad_nodes
master_nodes=`echo cluster nodes| $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password|awk '/ connected/'|awk '/master/'|awk '{print $1}'`
echo "master_nodes: "$master_nodes
slave_of_nodes=`echo cluster nodes| $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password|awk '/ connected/'|awk '/slave/'|awk '{print $4}'`
echo "slave_of_nodes: "$slave_of_nodes
con_slave_ips=`echo cluster nodes| $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password|awk '/ connected/'|awk '/slave/'|awk '{print $2}'`
echo "con_slave_ips: "$con_slave_ips
unfrag_slots=`echo cluster nodes| $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password|awk '/disconnected/'|awk '/master/'|awk '{print $9}'`
echo "unfrag_slots: "$unfrag_slots
con_ips=`echo cluster nodes| $HOME/bin/redis-cli -h ${ip} -p 6379 -a $password|awk '/ connected/'|awk '{print $2}'`
echo "con_ips: "$con_ips
break
fi
done
#reassgin master to slave whose master has been down
i=1
echo ">> reassgin master..."
for node in $slave_of_nodes
do
if [[ $master_nodes =~ $node ]]
then
echo "node "$node" has healthy master!"
i=$i+1
else
slave_ip= `echo $con_slave_ips | cut -d ' ' -f $i`
echo "reassgin master for "$node", ip"$slave_ip
`echo cluster replicate ${master_nodes[0]} | $HOME/bin/redis-cli -h ${slave_ip%:*} -p 6379 -a $password > /dev/null`
i=$i+1
fi
done
#forget_cluster_nodes
echo ">> cluster forget..."
for ip in $con_ips
do
echo "ip:"${ip%:*}
for node in $bad_nodes
do
echo "forget node:"$node
`echo cluster forget $node | $HOME/bin/redis-cli -h ${ip%:*} -p 6379 -a $password >/dev/null `
done
done
#add_new_nodes
echo ">> cluster meet..."
declare -a new_ips_arr
for ip in $ip_group
do
if [[ $con_ips =~ $ip ]]
then
echo "">/dev/null
else
echo "new node ip:"${ip%:*}
new_ips_arr=(${new_ips_arr[@]} ${ip%:*})
`echo cluster meet ${ip%:*} 6379 | $HOME/bin/redis-cli -h ${con_ips%%:*} -p 6379 -a $password > /dev/null`
fi
done
#allocate_slots
echo ">> cluster add slots..."
unfrag_slots_arr=($unfrag_slots)
for i in ${!unfrag_slots_arr[@]}
do
low=${unfrag_slots_arr[$i]%-*}
high=${unfrag_slots_arr[$i]#*-}
echo "low:"$low",high:"$high
for ((j=$low;j<=$high;j++))
do
`$HOME/bin/redis-cli -h ${new_ips_arr[$i]} -p 6379 -a $password cluster addslots $j > /dev/null`
done
echo "unset ip:"${new_ips_arr[$i]}
unset new_ips_arr[$i]
done
#cluster replicate
echo ">> cluster replicate..."
tmp_master=`echo $master_nodes|cut -d ' ' -f 1`
sleep 1
for ip in ${new_ips_arr[@]}
do
echo "cluster replicate for "$ip
`echo cluster replicate $tmp_master| $HOME/bin/redis-cli -h $ip -p 6379 -a $password > /dev/null`
done
五 移除node脚本内容。
#!/bin/bash
flags_noaddr_node_id="f2c9d6445359372bc7efbe16e53cdaa2d7b38923"
ip_port=$(redis-cli -h 192.168.1.4 -p 2008 cluster nodes | egrep -v 'noaddr|handshake|fail' | awk '{print $2}')
for i in $ip_port
do
eval $(echo $i | awk -F: '{printf("ip=%s;port=%s",$1,$2)}')
redis-cli -h $ip -p $port cluster forget $flags_noaddr_node_id
#flags_noaddr_node_id=$(redis-cli -h $ip -p $port cluster nodes | grep 'noaddr' | awk '{print $1; exit; }')
#test -n "$flags_noaddr_node_id" && echo $ip, $port, $flags_noaddr_node_id
done