redis主从+哨兵实现高可用

前言

Redis高可用常见的方式有两种:

  • 主从复制(Replication-Sentinel模式),至少需要3台服务器,1个Master节点,2个Slave节点,3个Sentinel节点,当有2个Sentinel认为Master挂了,则把其中1个Slave调整为Master
  • Redis集群(Redis-Cluster模式)Slave节点,至少需要6台服务器,即3个Master节点,每个Master至少都有1个Slave节点
  • 本文主要介绍redis主从+哨兵实现高可用
  • 我这里先准备3台服务器,安装目录为/www/server/redis
IP地址              redis版本(其他版本操作方式一致)
192.168.88.5(主)  redis 6.0.10
192.168.88.6(从)  redis 6.0.10
192.168.88.7(从)  redis 6.0.10

建议先通读本文后在实操

安装redis

...略

配置防火墙

# 查看防火墙状态
systemctl status firewalld 

# 启用防火墙
systemctl start firewalld

# 停用防火墙
systemctl stop firewalld

# 设置开机时启用防火墙
systemctl enable firewalld.service

# 设置开机时不启用防火墙
systemctl disable firewalld.service

# 防火墙开放6379端口
firewall-cmd --zone=public --add-port=6379/tcp --permanent
# 防火墙开放26379端口
firewall-cmd --zone=public --add-port=26379/tcp --permanent
# 重启防火墙使配置生效
systemctl restart firewalld

建议先关闭防火墙

配置主从

  • 连接某台服务器做主节点,我这里使用192.168.88.5做主节点
# 进入redis安装目录
cd /www/server/redis

# 备份配置文件
mv redis.conf bak_redis.conf

# 创建新的配置文件
vim redis.conf
  • 编辑配置文件内容,内容如下,其中带注释的配置需要你评估是否要修改,其余配置是redis 6.0.10的默认配置内容
requirepass 123123  # redis密码,不需要则删除该行
masterauth 123123  # 不需要密码则删除该行,哨兵模式需要用到
bind 0.0.0.0 # 所有ip可以访问redis
port 6379 # 端口
logfile /www/server/redis/logs/redis.log # 新建目录和日志文件

pidfile var/run/redis.pid
dir ./
dbfilename "dump.rdb"
protected-mode yes

maxclients 10000

tcp-backlog 511

timeout 0

tcp-keepalive 300

daemonize yes

supervised no

loglevel notice

databases 16

always-show-logo yes

save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes

rdbcompression yes

rdbchecksum yes

replica-serve-stale-data yes

replica-read-only yes

repl-diskless-sync no

repl-diskless-sync-delay 5

repl-disable-tcp-nodelay no

replica-priority 100

lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no

appendonly no

appendfilename "appendonly.aof"

appendfsync everysec

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

aof-load-truncated yes

aof-use-rdb-preamble yes

lua-time-limit 5000

slowlog-log-slower-than 10000

slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

list-max-ziplist-size -2

list-compress-depth 0

set-max-intset-entries 512

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

hll-sparse-max-bytes 3000

stream-node-max-bytes 4kb
stream-node-max-entries 100

activerehashing yes

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

hz 10

dynamic-hz yes

aof-rewrite-incremental-fsync yes

rdb-save-incremental-fsync yes

  • 重复以上步骤配置从节点服务器,从节点的redis.conf,只需额外添加如下内容
replicaof 192.168.88.5 6379  # 主节点的ip+端口,如果你的redis版本小于5.0,则把replicaof改为slaveof
  • 分别启动配置好的三个redis节点
# 启动redis
./src/redis-server ./redis.conf
  • 查看主从配置结果
# -a 指定密码连接redis
./src/redis-cli -a 123123

# 查看主从关系
info Replication
  • 如下可以看到当前role是master以及从节点的ip和端口等信息
[root@localhost ~]# cd /www/server/redis
[root@localhost redis]# ./src/redis-cli -a 123123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> info Replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.88.6,port=6379,state=online,offset=1688303,lag=0
slave1:ip=192.168.88.7,port=6379,state=online,offset=1688303,lag=0
master_replid:f035e75692e9f119d2c095fd4c79e606146b4c4f
master_replid2:bbd9b99985d844bc3e4031de95e9601b7d0513db
master_repl_offset:1688303
second_repl_offset:1653771
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:639728
repl_backlog_histlen:1048576

配置哨兵

  • 所有哨兵配置文件是一样的,所以每台服务器重复以下操作
# 进入redis安装目录
cd /www/server/redis

# 备份配置文件
mv sentinel.conf bak_sentinel.conf

# 创建新的配置文件
vim sentinel.conf
  • 编辑配置文件内容,内容如下
bind 0.0.0.0
port 26379
daemonize yes
pidfile /var/run/redis-sentinel.pid
logfile /www/server/redis/logs/sentinel.log #  新建目录和日志文件
dir ./
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 192.168.88.5 6379 2 # 配置主节点的ip和端口,2表示当有2个哨兵认为master失效,master才算真正失效
sentinel auth-pass mymaster 123123 # 访问密码
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 15000
  • 分别启动配置好的三个哨兵节点
# 启动redis
./src/redis-sentinel ./sentinel.conf
  • 查看哨兵配置结果
# -p 指定哨兵端口连接redis sentinel
./src/redis-cli -p 26379

# 使用哨兵查看master信息
sentinel master mymaster

# 使用哨兵查看slaves信息
sentinel slaves mymaster

验证哨兵自动故障转移

  • 连接Master节点服务器,当前这里是192.168.88.5
  • kill掉Master节点,注意不是kill哨兵
  • 通过哨兵查看新的master,./src/redis-cli -p 26379, 如下发现新的Master节点为192.168.88.6
[root@localhost redis]# ps -ef | grep redis
redis     17087      1  0 01:28 ?        00:00:00 /www/server/redis/src/redis-server 0.0.0.0:6379
root      17244      1  1 01:28 ?        00:00:01 /www/server/redis/src/redis-sentinel 0.0.0.0:26379 [sentinel]
root      17499   2227  0 01:30 pts/0    00:00:00 grep --color=auto redis
[root@localhost redis]# kill -9 17087
[root@localhost redis]# ./src/redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster
 1) "name"
 2) "mymaster"
 3) "ip"
 4) "192.168.88.6"
 5) "port"
 6) "6379"

也可以查看哨兵日志文件tailf ./logs/sentinel.log

应用高可用配置

当哨兵发现Master节点挂掉,会自动把其中一个Slave节点选举为Master节点,这个时候Master节点的ip就变了,所以当前我们还没有实现高可用,以下有两种方式处理ip变化

方式一:应用配置多个哨兵,通过哨兵获取真实的Master节点

  • 如下是spingboot应用集成redis哨兵的配置方式,代码层面不需要改动
spring:
  redis:
    password: 123123
    sentinel:
      master: mymaster
      nodes: 192.168.88.5:26379,192.168.88.6:26379,192.168.88.7:26379
  • 其他应用可能涉及到代码改造,请自行查找相关文档

方式二:VIP方式,当哨兵发现故障,把新的Master对应的ip到绑定虚拟ip上

注:VIP方式需要使用ROOT权限,若安全管理要求不允许使用ROOT则只能使用第一种方式

  1. 连接Master节点对应的服务器,手动给Master添加虚拟ip
# 查看网卡类型,即将用到,我这里是ens32,Centos6及以下默认为eth0
ls /etc/sysconfig/network-scripts | grep ifcfg-

# 这里我设置的虚拟ip是192.168.88.88
/sbin/ip addr add 192.168.88.88 dev ens32
sbin/arping -q -c 3 -U 192.168.88.88 -I ens32

# 查看虚拟ip是否设置成功
ip addr

# 测试,使用虚拟ip连接redis
/www/server/redis/src/redis-cli -h 192.168.88.88 -a 123123
  1. 分别给三台服务器创建脚本
# 创建脚本文件
touch /scripts/notify_master.sh
# 分配可执行权限
chmod +x /scripts/notify_master.sh
# 编辑文件
vim /scripts/notify_master.sh
  1. 编辑脚本内容如下,注意LOCAL_IP每台服务器都不一样,也就是每台服务器的这个配置文件都不一样
#!/bin/bash
MASTER_IP=$6  #哨兵调用该脚本会传6个参数,第6个参数是新主redis的ip地址
LOCAL_IP='192.168.88.5'  #本机ip,注意每个服务器都不一样
VIP='192.168.88.88' #虚拟ip 
NETMASK='24'
INTERFACE='ens32' #网卡类型
if [ ${MASTER_IP} = ${LOCAL_IP} ];then
    /sbin/ip  addr  add ${VIP}/${NETMASK}  dev ${INTERFACE}  #将VIP绑定到该服务器上
    /sbin/arping -q -c 3 -U ${VIP} -I ${INTERFACE}
    exit 0
else
   /sbin/ip  addr del  ${VIP}/${NETMASK}  dev ${INTERFACE}   #将VIP从该服务器上删除
   exit 0
fi
exit 1  #如果返回1,sentinel会一直执行这个脚本

注1:使用命令/scripts/notify_master.sh 1 1 1 1 1 192.168.88.5添加虚拟ip,验证脚本是否可以正常执行,其中192.168.88.5为本机ip, 因为脚本是获取$6(第6个参数),这里的 1 1 1 1 1是参数填充
注2:当哨兵选举新的Master后,会调用上面脚本,当${MASTER_IP}等于${LOCAL_IP}会给当前服务器添加虚拟ip,否则删除虚拟ip

  1. 编辑每个哨兵配置文件sentinel.conf,最后一行添加如下内容
sentinel client-reconfig-script mymaster /scripts/notify_master.sh
  1. 停止所有哨兵服务,然后重启所有哨兵
  2. 停止Master节点,查看虚拟ip是否绑定到新Master对应的服务器上
# 每台服务器分别执行ip addr查看那台服务器绑定了虚拟ip
ip addr | grep 88
  1. 最后,应用通过虚拟ip连接redis即可

使用chkconfig设置开机启动

设置哨兵开机启动

  • 创建脚本
cd /etc/rc.d/init.d
vim redis-sentinel
  • 编辑脚本内容
#!/bin/sh
#chkconfig: 2345 80 90
#description: Redis Sentinel Service
REDISPORT=26379
REDISPATH=/www/server/redis #注意修改为你的redis安装目录
EXEC=${REDISPATH}/src/redis-sentinel
CLIEXEC=${REDISPATH}/src/redis-cli
CONF="${REDISPATH}/sentinel.conf"
PIDFILE=/var/run/redis-sentinel.pid #注意修改为你的哨兵pid文件
case "$1" in
  start)
    if [ -f $PIDFILE ]
    then
        echo "$PIDFILE exists, process is already running or crashed"
    else
        echo "Starting Redis sentinel..."
        $EXEC $CONF
    fi
    ;;
  stop)
    if [ ! -f $PIDFILE ]
    then
        echo "$PIDFILE does not exist, process is not running"
    else
        PID=$(cat $PIDFILE)
        echo "Stopping ..."
        $CLIEXEC -p $REDISPORT shutdown
        while [ -x /proc/${PID} ]
        do
          echo "Waiting for Redis-sentinel to shutdown ..."
          sleep 1
        done
        echo "Redis-sentinel stopped"
    fi
    ;;
  *)
    echo "Please use start or stop as first argument"
    ;;
esac

  • 设置开机启动
# 给脚本添加可执行权限
chmod +x redis-sentinel

# 验证脚本能否关闭哨兵服务
./redis-sentinel stop

# 验证脚本能否启动哨兵服务
./redis-sentinel start

# 把哨兵添加到chkconfig列表中
chkconfig --add redis-sentinel

# 设置哨兵服务开机启动
chkconfig redis-sentinel on

设置redis开机启动

  • 创建脚本
cd /etc/rc.d/init.d
vim redis
  • 编辑脚本内容
#!/bin/sh
# chkconfig: 2345 56 26
# description: Redis Service

### BEGIN INIT INFO
# Provides:          Redis
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts Redis
# Description:       starts the BT-Web
### END INIT INFO

# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.

REDISPATH=/www/server/redis #注意修改为你的redis安装目录
PIDFILE=/www/server/redis/redis.pid #注意修改为你的redis pid文件
CONF="${REDISPATH}/redis.conf"
REDISPORT=$(cat $CONF |grep port|grep -v '#'|awk '{print $2}')
REDISPASS=$(cat $CONF |grep requirepass|grep -v '#'|awk '{print $2}')
REDISHOST=$(cat $CONF |grep bind|grep -v '#'|awk '{print $2}')
if [ "$REDISPASS" != "" ];then
    REDISPASS=" -a $REDISPASS"
fi
if [ -f "${REDISPATH}/start.pl" ];then
    STARPORT=$(cat ${REDISPATH}/start.pl)
else
    STARPORT="6379"
fi
EXEC=${REDISPATH}/src/redis-server
CLIEXEC="${REDISPATH}/src/redis-cli -h ${REDISHOST} -p ${REDISPORT}${REDISPASS}"
echo "${REDISPATH}/src/redis-cli -h ${REDISHOST} -p ${REDISPORT}${REDISPASS}"

redis_start(){
    if [ -f "${REDISPATH}/redis.pid" ]; then
        ps -p $(cat ${PIDFILE}) > /dev/null 2>&1
        if [ $? -ne "0" ]; then
            rm -f ${PIDFILE}
        else
            echo "redis is running! ($(cat ${PIDFILE}))"
            exit 0
        fi
    fi
    echo "Starting redis server..."
    sudo -u redis $EXEC $CONF
    echo ${REDIS_PORT} > ${REDISPATH}/start.pl
    echo "Starting redis success!"
}
redis_status(){
    if [ -f "${REDISPATH}/redis.pid" ]; then
        ps -p $(cat ${PIDFILE}) > /dev/null 2>&1
        if [ $? -ne "0" ]; then
            echo "Redis is not running, buy pid file is exits ${PIDFILE}"
            exit 1
        else
            echo "redis is running! ($(cat ${PIDFILE}))"
            exit 0
        fi
    else
        echo "redis is stopped"
        exit 0
    fi
}
redis_stop(){
    echo "Stopping ..."
    $CLIEXEC shutdown
    sleep 1
    pkill -9 redis-server
    rm -f ${PIDFILE}
    echo "Redis stopped"
}

case "$1" in
    start)
        redis_start
        ;;
    stop)
        redis_stop
        ;;
    status)
        redis_status
        ;;
    restart|reload)
        redis_stop
        sleep 0.3
        redis_start
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac

  • 设置开机启动
# 给脚本添加可执行权限
chmod +x redis

# 验证脚本能否关闭redis
./redis stop

# 验证脚本能否启动redis
./redis start

# 把redis添加到chkconfig列表中
chkconfig --add redis

# 设置redis服务开机启动
chkconfig redis on

其他

# 查看所有chklist中服务
chkconfig --list

# 关闭xxx开机启动
chkconfig xxx off

# 把xxx从chkconfig列表中删除
chkconfig --del xxx

最后

  • 至此已经实现redis高可用
  • 参考1:Redis高可用技术解决方案
  • 参考2:redis主从+哨兵+vip实现高可用
  • 补充1:本文在做redis高可用时,经常会依次操作每台服务器执行相同的命令,重复操作显然效率低,这里推荐-linux自动化运维工具ansible,批量操作多台服务器执行命令
  • 补充2:服务器使用WinSCP传输文件效率低下,如你使用Xshell命令行工具,可以使用如下方式实现文件上传与下载
    # 安装lrzsz
    yum -y install lrzsz
    rz # 上传
    sz fileName # 下载,fileName是你要下载的文件名
    

安全管理要求

在对服务器安全要求比较严格的情况下,需要考虑以下几点

  1. redis的安装及操作不能使用root权限
  2. redis不能使用默认端口
  3. 配置文件不能使用bind 0.0.0.0,必须制定ip
  4. 密码必须不能使用弱密码

推荐阅读更多精彩内容