阅读量:0
前言
Redis一般情况下的部署方案
方案 | 优点 | 缺点 |
---|---|---|
单机 | 部署简单、只需一台服务器 | 当该服务器出现故障则无法提供Redis服务 |
哨兵模式 | 当无法连接的Redis节点小于一半(不包含一半)时任然可以提供服务 | 需要3台及以上服务器 |
集群模式 | 包含哨兵模式的优点,又具备负载均衡的特性 | 包含哨兵模式的缺点,且不具备在线扩展的能力,部署难度也相对提高 |
那么有没有一种类似于mysql的互为主从的模式,仅需要2台服务器就能实现高可用的方案呢?
借鉴了网络上其他方案,并砍去了keepalived,采用crontab定时任务+iptables定时任务的方式
前期准备
服务器 | IP | 系统版本 |
---|---|---|
服务器A | 192.168.88.102 | Ubuntu22 |
服务器B | 192.168.88.103 | Ubuntu22 |
名称 | 版本 | 下载地址 | 包类型 | 备注 |
---|---|---|---|---|
Redis | 7.0.5 | 源码包 | ||
nginx | 1.22.1 | 源码包 |
安装reids
A、B服务器操作一致
解压缩
# 将安装包上次到服务器后,切换目录至安装包同级目录 # 解压 tar -zxvf redis-7.0.5.tar.gz
安装编译环境
# Ubuntu apt-get install make gcc pkg-config libc6-dev # CentOS # yum -y install gcc automake autoconf libtool make
编译
# 切换到源码根目录 cd redis-7.0.5 # 编译 make # 安装 make PREFIX=/usr/local/redis install # 将默认配置文件复制到安装目录 cp redis.conf /usr/local/redis/ # 如编译失败后,再次编译前请先清除残留文件 # Ubuntu # make distclean # CentOS # make clean
修改配置文件
# 切换目录 cd /usr/local/redis/ # 编辑文件 vi redis.conf # 修改内容如下:
# 允许其他设备远程连接redis bind 0.0.0.0 # 允许后台启动 daemonize yes # 修改日志存放目录,不需要日志可改为logfile "/dev/null" logfile "/var/log/redis_6379.log" # 数据持久化目录 dir /usr/local/redis/ # 设置密码,如: requirepass D83544E45CA39C7653BF21612FAD0FD1
启动和测试
# 启动 ./bin/redis-server redis.conf # 尝试连接 -a 后接的是密码 ./bin/redis-cli -a D83544E45CA39C7653BF21612FAD0FD1 # 设置一个变量a,值为1 set a 1 # 查询变量a的值 get a # 退出 quit
停止
# D83544E45CA39C7653BF21612FAD0FD1为密码 ./bin/redis-cli -a D83544E45CA39C7653BF21612FAD0FD1 shutdown
设置防火墙
Ubuntu22
# 若未启用可用如下命令启动防火墙 ufw enable # 允许所有外来访问以tcp协议连接6379端口 ufw allow 6379/tcp
CentOS7
# 若未启用可用如下命令启动防火墙 systemctl start firewalld # 允许所有外来访问以tcp协议连接6379端口 firewall-cmd --znotallow=public --add-port=80/tcp --permanent # 重新加载防火墙规则 firewall-cmd --reload
配置主从
将redis.conf文件复制两份分别为slave.conf和master.conf
cp redis.conf slave.conf cp redis.conf master.conf
分别对两台服务器的slave.conf文件做如下修改
A服务器slave.conf
# 设置B服务器为主服务 replicaof 192.168.88.103 6379 # 配置连接密码,即主服务的密码 masterauth D83544E45CA39C7653BF21612FAD0FD1
B服务器slave.conf
# 设置A服务器为主服务 replicaof 192.168.88.102 6379 # 配置连接密码,即主服务的密码 masterauth D83544E45CA39C7653BF21612FAD0FD1
启动验证
A服务器以主身份启动
./bin/redis-server master.conf
B服务器以从身份启动
./bin/redis-server slave.conf
分别连接两台服务器的Redis
# 连接A ./bin/redis-cli -h 192.168.88.102 -a D83544E45CA39C7653BF21612FAD0FD1 # 连接B ./bin/redis-cli -h 192.168.88.103 -a D83544E45CA39C7653BF21612FAD0FD1
在A中设置变量y 值为3
set y 3
在B中查询变量y 得到值为3
get y
证明主从设置成功,注意主服务提供查和写功能,但从服务只提供查功能
实现主从切换
A、B服务器操作一致
在redis根目录上创建redis.sh文件
vi /usr/loacl/redis/redis.sh
写入如下内容
#!/bin/bash # Redis 启动脚本 # # 脚本当前目录 PRG="$0" while [ -h "$PRG" ]; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`/"$link" fi done PRGDIR=`dirname "$PRG"` SH_DIR=`cd "$PRGDIR" >/dev/null; pwd` APP_NAME="redis-server" # 根路径 #APP_PATH=/usr/local/redis APP_PATH=$SH_DIR BIN_PATH=$APP_PATH/bin # 配置文件路径 CONF_PATH=$APP_PATH/ CONF_FILE_PATH=$CONF_PATH/redis.conf MASTER_CONF_FILE_PATH=$CONF_PATH/master.conf SLAVE_CONF_FILE_PATH=$CONF_PATH/slave.conf # 运行端口 - 默认 6379 APP_PORT=6379 # Redis 密码 - 默认为空 APP_PASS="D83544E45CA39C7653BF21612FAD0FD1" ## 从配置文件,读取端口、密码 PORT_STR=$(cat $CONF_FILE_PATH | grep -E '^port'| sed 's/port \([0-9]*\).*/\1/g' ) PASS_STR=$(cat $CONF_FILE_PATH | grep -E '^requirepass' | sed 's/.*requirepass \(.*\)/\1/g' | sed 's/"//g') #echo "PORT_STR: $PORT_STR" #echo "PASS_STR: $PASS_STR" if [ -n "$PORT_STR" ]; then APP_PORT=$PORT_STR; fi if [ -n "$PASS_STR" ]; then APP_PASS=$PASS_STR; fi # 处理密码连接字符串 PASS_KEY="" if [ -n "$APP_PASS" ]; then PASS_KEY="-a $APP_PASS" fi echo -e "\e[35m======================================================================\e[0m" #使用说明 用来提示参数 usage(){ echo -e "\e[33mAPP_PATH:\e[0m \e[36m $APP_PATH \e[0m" echo -e "\e[33mUsage:\e[0m \e[36m $0 [start|stop|restart] \e[0m" echo -e "\e[33mUsage:\e[0m \e[36m $0 [status|running|role|slaveup] \e[0m" echo -e "\e[33mUsage:\e[0m \e[36m $0 [tomaster|toslave|startmaster|startslave|startauto] \e[0m" echo -e "\e[35m----------------------------------------------------------------------\e[0m" echo -e " -\e[32m start \e[0m: 正常启动 Redis,$BIN_PATH/redis-server $CONF_FILE_PATH" echo -e " -\e[32m stop \e[0m: 停止 Redis, $BIN_PATH/redis-cli shutdown" echo -e " -\e[32m restart \e[0m: 重启 Redis, stop && start" echo -e "\e[35m----------------------------------------------------------------------\e[0m" echo -e " -\e[32m status \e[0m: 当前运行状态、对应角色类型" echo -e " -\e[32m running \e[0m: 当前运行状态,运行中返回 0,否则返回 1" echo -e " -\e[32m role \e[0m: 当前运行对应角色类型,master 返回 0, slave 返回 1, 其它 返回2, redis 未运行 返回9" echo -e " -\e[32m slaveup \e[0m: 当前运行状态为slave时,master_link_status是否为up (是则返回0,否则为1)" echo -e " -\e[32m masterup \e[0m: 判断另一个节点当前角色 (master则返回0,slave则为1)" echo -e "\e[35m----------------------------------------------------------------------\e[0m" echo -e " -\e[32m tomaster \e[0m: 运行状态转换角色类型为【master】" echo -e " -\e[32m toslave \e[0m: 运行状态转换角色类型为【slave】" echo -e " -\e[32m startmaster\e[0m: 未运行时,以【master】角色类型运行" echo -e " -\e[32m startslave \e[0m: 未运行时,以【slave】角色类型运行" echo -e " -\e[32m startauto \e[0m: 未运行时,根据slave.conf的master节点状态,自动判断当前节点以【master/slave】角色类型运行" echo "" exit 1 } #检查程序是否已运行 is_running(){ # 根据应用名称,获取进程pid (当存在多个时不适用) pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}'` # 根据端口号获取对应 pid #pid=$(lsof -t -i:$APP_PORT) #pid=$(lsof -i:$APP_PORT | grep *: | awk '{print $2}') #如果不存在返回1 存在返回0 if [ -z "${pid}" ]; then return 1 else return 0 fi } # 启动方法 start(){ is_running if [ $? -eq "0" ]; then echo "$APP_NAME - [PORT: $APP_PORT] is already running . pid=${pid}" else $BIN_PATH/redis-server $CONF_FILE_PATH sleep 1 status if [ $? -eq "0" ]; then echo "$APP_NAME - [PORT: $APP_PORT] start success . pid=${pid}" else echo "$APP_NAME - [PORT: $APP_PORT] start fail ." fi fi } # 用 master.conf 的配置文件替换 redis.conf 后启动 startmaster(){ echo "准备以【 master 】模式启动,复制替换 redis.conf 文件..." cp -f $MASTER_CONF_FILE_PATH $CONF_FILE_PATH sleep 1 start } # 用 slave.conf 的配置文件替换 redis.conf 后启动 startslave(){ echo "准备以【 slave 】模式启动,复制替换 redis.conf 文件..." cp -f $SLAVE_CONF_FILE_PATH $CONF_FILE_PATH sleep 1 start } # 自动判断当前启用 master 还是 slave 模式 startauto(){ is_running if [ $? -eq "0" ]; then echo "${APP_NAME} - [PORT: ${APP_PORT}] is already running . pid=${pid}" exit 1 fi # 读取 slave.conf 配置,获取对应 master 配置 arr=(`cat ${SLAVE_CONF_FILE_PATH} | grep ^replicaof`) master_host=${arr[1]} master_port=${arr[2]} authcmd=$(cat $SLAVE_CONF_FILE_PATH | grep ^masterauth | sed 's/masterauth //g' | sed 's/"//g') auth_key="" if [ -n "$authcmd" ]; then auth_key="-a $authcmd" fi echo "设置 master -h $master_host -p $master_port $auth_key " # 判断 master 配置中的服务,对应角色类型 roleName=$($BIN_PATH/redis-cli -h $master_host -p $master_port $auth_key info | grep role: | sed 's/.$//g') echo "slave 配置的 master 当前类型: $roleName" if [ "$roleName" = "role:master" ]; then startslave else startmaster fi } # 自动判断另一个节点 master 还是 slave 模式 masterup(){ # 读取 slave.conf 配置,获取对应 master 配置 arr=(`cat ${SLAVE_CONF_FILE_PATH} | grep ^replicaof`) master_host=${arr[1]} master_port=${arr[2]} authcmd=$(cat $SLAVE_CONF_FILE_PATH | grep ^masterauth | sed 's/masterauth //g' | sed 's/"//g') auth_key="" if [ -n "$authcmd" ]; then auth_key="-a $authcmd" fi echo "设置 master -h $master_host -p $master_port $auth_key " # 判断 master 配置中的服务,对应角色类型 roleName=$($BIN_PATH/redis-cli -h $master_host -p $master_port $auth_key info | grep role: | sed 's/.$//g') echo "slave 配置的 master 当前类型: $roleName" if [ "$roleName" = "role:master" ]; then return 0 else return 1 fi } # 停止方法 stop(){ is_running if [ $? -eq "0" ]; then echo "$APP_NAME - [PORT: $APP_PORT] is running . pid=${pid} ; stoping..." # 执行 shutdown $BIN_PATH/redis-cli $PASS_KEY shutdown sleep 1 is_running if [ $? -eq "0" ]; then echo "$APP_NAME - [PORT: $APP_PORT] stop fail . pid=${pid}" else echo "$APP_NAME - [PORT: $APP_PORT] stop success ." fi else echo "$APP_NAME - [PORT: $APP_PORT] is not running ." fi } #重启 restart(){ stop start } # 判断运行的角色[ master, slave ] # master 返回 0, slave 返回 1, 其它 返回2, redis 未运行 返回9 role(){ is_running if [ $? -eq "0" ]; then roleName=$($BIN_PATH/redis-cli $PASS_KEY info | grep role: | sed 's/.$//g') if [ "$roleName" = "role:master" ]; then return 0 elif [ "$roleName" = "role:slave" ]; then return 1 else echo "ERROR: unknow role: [$roleName] !!!" return 2 fi else echo "$APP_NAME - [PORT: $APP_PORT] is not running ." return 9 fi } # 判断当前运行的 是否为 slave 且 正常连接上 master # 为简化逻辑,此处实现仅 在 info 信息中匹配 master_link_status:up (运行正常返回0,否则为1) # 即正常调用前,应该要知道此节点正常运行 且 为 slave slaveup(){ linkStatus=$($BIN_PATH/redis-cli $PASS_KEY info | grep master_link_status:up) if [ -z "$linkStatus" ]; then #echo "redis master link is down" return 1 else #echo "redis master link is up" return 0 fi } # Change Redis to master; Use Command: replicaof no one tomaster(){ role mode=$? if [ $mode -eq "0" ]; then echo "$APP_NAME - [PORT: $APP_PORT] 已经是[ master ] 不必要切换 ." elif [ $mode -eq "1" ]; then # 切换配置文件 - 避免下次重启后变化 cp -f $MASTER_CONF_FILE_PATH $CONF_FILE_PATH # 执行切换为 主服务 $BIN_PATH/redis-cli $PASS_KEY replicaof no one sleep 1 status else echo "$APP_NAME - [PORT: $APP_PORT] 不支持操作状态 [$mode]." fi } # Change Redis to slave; Use Command: replicaof masterip masterport toslave(){ role mode=$? if [ $mode -eq "1" ]; then echo "$APP_NAME - [PORT: $APP_PORT] 已经是[ slave ] 不必要切换 ." elif [ $mode -eq "0" ]; then # 切换配置文件 - 避免下次重启后变化 cp -f $SLAVE_CONF_FILE_PATH $CONF_FILE_PATH authcmd=$(cat $CONF_FILE_PATH | grep ^masterauth | sed 's/"//g') echo "设置 master 连接密码. $authcmd" # 执行更新密码 $BIN_PATH/redis-cli $PASS_KEY config set $authcmd repcmd=$(cat $CONF_FILE_PATH | grep ^replicaof) echo "准备切换为[ slave ], $repcmd" # 执行切换为 从服务 $BIN_PATH/redis-cli $PASS_KEY $repcmd sleep 1 status else echo "$APP_NAME - [PORT: $APP_PORT] 不支持操作状态 [$mode]." fi } # 判断状态 status(){ is_running if [ $? -eq "0" ]; then echo "$APP_NAME - [PORT: $APP_PORT] is running . pid=${pid}" role if [ $? -eq "0" ]; then echo "Redis is master role" elif [ $? -eq "1" ]; then echo "Redis is slave role" else echo "Redis is unknow role" fi else echo "$APP_NAME - [PORT: $APP_PORT] is not running ." fi } # 根据输入的参数执行对应的方法 case "$1" in "start") start ;; "stop") stop ;; "running") is_running ;; "status") status ;; "role") role ;; "restart") restart ;; "tomaster") tomaster ;; "toslave") toslave ;; "startmaster") startmaster ;; "startslave") startslave ;; "startauto") startauto ;; "slaveup") slaveup ;; "masterup") masterup ;; *) usage ;; esac
赋予可执行权限
chmod +x redis.sh
启动测试
A、B服务器分别重启服务
./redis.sh restart
B服务器将从服务切换为主
./redis.sh tomaster
A服务将主服务改为从
./redis.sh toslave
分别连接2个Redis应用
B服务器连接B服务并设置一个变量t 值为2
A服务器连接B服务并查找t变量,得到值2
自动切换主从
A、B服务器操作一致
创建检测脚本
vi /usr/local/redis/vip_keepalived.sh
写入如下内容:
#!/bin/bash BASE_PATH=/usr/local/redis # 转发规则生效 iptables_up(){ while ([ `iptables -t nat -nL PREROUTING --line | grep 6379$ | grep 6378 | wc -l` -gt 1 ]) do iptables -t nat -D PREROUTING -p tcp --dport 6378 -j REDIRECT --to-port 6379 done while ([ `iptables -t nat -nL OUTPUT --line | grep 6379$ | grep 6378 | wc -l` -gt 1 ]) do iptables -t nat -D OUTPUT -p tcp --dport 6378 -j REDIRECT --to-port 6379 done if [ `iptables -t nat -nL PREROUTING --line | grep 6379$ | grep 6378 | wc -l` -eq "0" ]; then iptables -t nat -A PREROUTING -p tcp --dport 6378 -j REDIRECT --to-port 6379 fi if [ `iptables -t nat -nL OUTPUT --line | grep 6379$ | grep 6378 | wc -l` -eq "0" ]; then iptables -t nat -A OUTPUT -p tcp --dport 6378 -j REDIRECT --to-port 6379 fi } # 删除转发规则 iptables_down(){ while([ `iptables -t nat -nL PREROUTING --line | grep 6379$ | grep 6378 | wc -l` -gt 0 ]) do iptables -t nat -D PREROUTING -p tcp --dport 6378 -j REDIRECT --to-port 6379 done while([ `iptables -t nat -nL OUTPUT --line | grep 6379$ | grep 6378 | wc -l` -gt 0 ]) do iptables -t nat -D OUTPUT -p tcp --dport 6378 -j REDIRECT --to-port 6379 done } # 检查是否切换角色 check(){ # 判断运行的角色[ master, slave ] master 返回 0, slave 返回 1, 其它 返回2, redis 未运行 返回9 $BASE_PATH/redis.sh role redisRole=$? echo "当前 redis role值: [$redisRole]" if [ $redisRole -eq "0" ]; then iptables_up # redis 有运行,且为 master 主节点,返回 0 $BASE_PATH/redis.sh masterup if [ $? -eq "0" ]; then $BASE_PATH/redis.sh toslave iptables_down fi return 0 elif [ $redisRole -eq "1" ]; then iptables_down #echo " redis 有运行,且为 slave 状态" # 判断 master_link_status 是否为 up $BASE_PATH/redis.sh slaveup if [ $? -eq "0" ]; then #echo "slave 连接 master 正常" return 1; else # 判断 另一个节点的role,只有另一个节点不为master才能将本节点切换为master $BASE_PATH/redis.sh masterup if [ ! $? -eq "0" ]; then # slave 连接 master 已断开,修改本节点为 master $BASE_PATH/redis.sh tomaster iptables_up return 0 else iptables_down return 1 fi fi else iptables_down #echo " 其它状态,直接返回对应结果[$redisRole]" return $redisRole fi # 删除转发规则 iptables_down #echo "redis 未运行,直接返回 1" return 1 } check
赋予可执行权限
chmod +x /usr/local/redis/vip_keepalived.sh
创建定时任务
echo "* * * * * /usr/local/redis/vip_keepalived.sh > /tmp/check_redis.log"| crontab
安装nginx
可参考以往安装,此处简略描述
注意安装时加入 --with-stream
#安装nginx核心模块 tar -zxvf nginx-1.12.0.tar.gz cd nginx-1.12.0 # 注意加入tcp代理的模块即可 ./configure --with-stream make make install chmod a+rwx -R /usr/local/nginx/logs/ chmod a+rwx -R /usr/local/nginx/ /usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx cat /usr/local/nginx/logs/error.log # 设置开机自启动 cp $PRGDIR/nginx /etc/init.d/ chmod +x /etc/init.d/nginx chkconfig --add nginx chkconfig nginx on
通过nginx实现高可用
在nginx配置文件末尾加入:
stream { upstream backend-redis { # A服务器redis映射端口 server 192.168.88.102:6378; # B服务器redis映射端口 server 192.168.88.103:6378; } server { #代理端口 6380 程序之后访问redis 都通过 127.0.0.1:6380 listen 6380; proxy_pass backend-redis; } }
重启nginx后生效
/usr/local/nginx/sbin/nginx -s quit /usr/local/nginx/sbin/nginx
总结
方案 | 优点 | 缺点 |
---|---|---|
单机 | 部署简单、只需一台服务器 | 当该服务器出现故障则无法提供Redis服务 |
哨兵模式 | 当无法连接的Redis节点小于一半(不包含一半)时任然可以提供服务 | 需要3台及以上服务器 |
集群模式 | 包含哨兵模式的优点,又具备负载均衡的特性 | 包含哨兵模式的缺点,且不具备在线扩展的能力,部署难度也相对提高 |
互为主从(keepalived版) | 2台服务器即可实现,基本可实现非人为干预高可用 | 从服务出现问题到切换完成需要一定时间,可能需要1.2秒。部署需要而外申请一个IP用作VIP |
互为主从(上文方式) | 2台服务器即可实现,基本可实现非人为干预高可用,不用而外申请IP | crontab最短为1分钟执行一次,也就是说从出问题到切换成功可能需要1分钟(当然也可以用脚本循环,不交给定时任务),该服务会被绑定到nginx上一但连接的Nginx出问题,该服务也会无法访问 |
最合适当下的才是最好的