本文写于 2014 年。 当时 Redis 官方高可用方案 Redis Sentine,处于 beta 阶段。
|
需求说明
在传统的非云化的IT环境中,Redis 高可用主要使用 Keepalived 搭建,Keepalived 向 Redis 客户端提供 VIP,监控主备机工作状态,VIP 自动漂移,实现容灾切换。
在商业公有云环境中,用户购买虚拟机,云平台会自动分配内网 IP,网络管理完全由平台控制,普通用户无法自由配置 IP 地址,VIP 作为 Keepalived 方案中重要的条件,在商业公有云中无法满足。
在阿里云、腾讯云搭建环境中,我们就碰到上面的问题。
解决方案是,利用 shell 脚本实现 Keepalived 的功能。
需求实现
架构图
工作流程
1.使用启动脚本启动 Redis 主机; 2.使用启动脚本启动 Redis 备机,向当前主机同步; 3.Hyperic 定时调用 Redis 检查脚本判断主备机状态; 4.主机异常时,Redis 备机自动切换为主机,触发告警; 5.主机恢复时,使用启动脚本重新启动主机,向当前主机(原备机)同步,同步完成后,再切换为主机; 6.备机异常时,触发告警; 7.备机恢复时,使用启动脚本重新启动备机,向当前主机同步;
|
实现原理
1.SLB 负载两台 Ha 服务器 SLB 为何不直接负载两台 Redis 服务器? Redis 双机具备主从关系,一般 SLB 服务负载均衡策略较为简单,仅支持轮询或最小连接数策略,不支持主备模式,无法满足 Redis 双机负载均衡要求。 2.Ha 服务器负载两台Redis服务器 Ha 服务器运行开源负载均衡组件 Haproxy,通过配置文件可实现灵活的负载均衡策略, 针对 Redis 服务,使用主备模式。
|
具体配置
1.SLB,轮询模式,前端服务端口 6379,后端转发端口 6379,开启会话保持;
2.Haproxy,主备模式,前端服务端口 6379,后端转发端口 6379,监控端口 16379;
在 Redis 本地存在大量持久化数据的情况下,重新启动时,数据加载的时间耗时较长,在这个过程中,无法正常对外提供服务。
如果使用 Redis 服务端口 6379 作为监控端口,当组件启动时,Ha 会认为 Redis 可正常提供服务,实际上,只有组件启动且数据全部加载完成,才能正常对外提供服务,数据加载时间同数据量正相关,无法准确估计,所以监控端口使用 16379,当数据加载完成时,由脚本控制,实现对该端口的监听。
Haproxy 配置
listen redis bind 0.0.0.0:6379 mode tcp balance roundrobin server redis1 10.161.58.215:6379 weight 1 maxconn 10240 check port 16379 check inter 10s server redis2 10.161.69.45:6379 weight 1 maxconn 10240 check port 16379 check inter 10s backup
|
3.Redis,主备机角色预置
主机
备机
主机启动脚本
#!/bin/bash
if test $( pgrep -f "redis-server" | wc -l ) -eq 0 then echo "redis 进程不存在,开始启动进程"
/usr/local/redis/src/redis-server /etc/redis.conf echo "redis启动命令执行完成"
else echo "redis进程已存在" fi
i=0 while [ "$i" -lt 60 ] do
ALIVE=`/usr/local/redis/src/redis-cli ping`
if [ "$ALIVE" == "PONG" ]; then echo "redis启动和预热状态检查,进度:$i/60" i=`expr $i + 1` sleep 1
else echo “redis启动失败,请检查...” sleep 1 fi
done
if test $( nc -w 0 10.161.69.45 6379 && echo 0 || echo 1 ) -eq 0
then echo "slave当前是启动状态" echo "master开始从slave开始同步"
SYNC=`/usr/local/redis/src/redis-cli SLAVEOF 10.161.69.45 6379`
if [ "$SYNC" == "OK" ] then echo "master向slave发送同步指令成功"
elif [ "$SYNC" == "OK Already connected to specified master" ] then echo "master同slave已经是同步状态" else echo "master向slave发送同步指令失败" echo "$SYNC" fi
else echo "slave当前不是启动状态" echo "master无需同步" echo "shell进程退出"
echo "启动监听端口" nohup ./port_redis.sh > /dev/null &
exit fi
while [ "$SYNCTAG" != "1" ] do
SYNCINFO=`/usr/local/redis/src/redis-cli info` SYNCTAG=`echo "$SYNCINFO"|grep "master_link_status:up"|wc -l` done
echo "master从slave同步成功"
MASTER=`/usr/local/redis/src/redis-cli SLAVEOF NO ONE` if [ "$MASTER" == "OK" ] then
echo "启动监听端口" nohup ./port_redis.sh > /dev/null &
fi
|
主机检查脚本:
#!/bin/bash
if test $( nc -w 0 127.0.0.1 6379 && echo 0 || echo 1 ) -eq 0 then echo 0 else
/usr/local/redis/src/redis-cli -h 10.161.69.45 -p 6379 SLAVEOF NO ONE > /dev/null
if test $( pgrep -f "port_redis" | wc -l ) -eq 1 then kill -9 `ps -ef|grep "port_redis"|grep -v "grep" |awk '{print $2}'` fi
if test $( pgrep -f "nc -l 16379" | wc -l ) -eq 1 then kill -9 `ps -ef|grep "nc -l 16379"|grep -v "grep" |awk '{print $2}'` fi
echo 1 fi
|
备机启动脚本
#!/bin/bash
if test $( pgrep -f "redis-server" | wc -l ) -eq 0 then echo "redis 进程不存在,开始启动进程"
/usr/local/redis/src/redis-server /etc/redis.conf echo "redis启动命令执行完成"
else echo "redis进程已存在" fi
i=0 while [ "$i" -lt 60 ] do
ALIVE=`/usr/local/redis/src/redis-cli ping`
if [ "$ALIVE" == "PONG" ]; then echo "redis启动和预热状态检查,进度:$i/60" i=`expr $i + 1` sleep 1
else echo “redis启动失败,请检查...” sleep 1 fi
done
if test $( nc -w 0 10.161.58.215 6379 && echo 0 || echo 1 ) -eq 0
then echo "master当前是启动状态" echo "slave开始从master开始同步"
SYNC=`/usr/local/redis/src/redis-cli SLAVEOF 10.161.58.215 6379`
if [ "$SYNC" == "OK" ] then echo "slave向master发送同步指令成功"
elif [ "$SYNC" == "OK Already connected to specified master" ] then echo "slave同master已经是同步状态" else echo "slave向master发送同步指令失败" echo "$SYNC" fi
else echo "master当前不是启动状态" echo "slave无需同步" echo "shell进程退出"
echo "启动监听端口" nohup ./port_redis.sh > /dev/null &
exit fi
while [ "$SYNCTAG" != "1" ] do
SYNCINFO=`/usr/local/redis/src/redis-cli info` SYNCTAG=`echo "$SYNCINFO"|grep "master_link_status:up"|wc -l` done
echo "slave从master同步成功"
echo "启动监听端口" nohup ./port_redis.sh > /dev/null &
|
备机检查脚本
#!/bin/bash
MASTERTAG=`nc -w 0 10.161.58.215 6379 && echo 0 || echo 1` SLAVETAG=`nc -w 0 127.0.0.1 6379 && echo 0 || echo 1`
if [ $MASTERTAG -eq 0 ] && [ $SLAVETAG -eq 0 ] then
echo 0
elif [ $MASTERTAG -eq 1 ] && [ $SLAVETAG -eq 0 ] then
/usr/local/redis/src/redis-cli -h 127.0.0.1 -p 6379 SLAVEOF NO ONE > /dev/null
echo 0
elif [ $MASTERTAG -eq 0 ] && [ $SLAVETAG -eq 1 ] then
if test $( pgrep -f "port_redis" | wc -l ) -eq 1 then kill -9 `ps -ef|grep "port_redis"|grep -v "grep" |awk '{print $2}'` fi
if test $( pgrep -f "nc -l 16379" | wc -l ) -eq 1 then kill -9 `ps -ef|grep "nc -l 16379"|grep -v "grep" |awk '{print $2}'` fi
echo 1
else
if test $( pgrep -f "port_redis" | wc -l ) -eq 1 then kill -9 `ps -ef|grep "port_redis"|grep -v "grep" |awk '{print $2}'` fi
if test $( pgrep -f "nc -l 16379" | wc -l ) -eq 1 then kill -9 `ps -ef|grep "nc -l 16379"|grep -v "grep" |awk '{print $2}'` fi
echo 1
fi
|
监控端口启动脚本
#!/bin/bash while [ 1 ] do if test $( nc -w 0 127.0.0.1 16379 && echo 0 || echo 1 ) -eq 0 then exit else nc -l 16379 fi done
|