主从复制与集群模式

Redis 主从复制

Redis 主从复制概述

  • 一个master可以有多个slave
  • 一个slave只能有一个master
  • 数据流是单向的,master到slave

Redis 主从复制工作原理

  • 从服务器 从 主服务器同步数据,此过程是非阻塞的

  • redis主从复制分为全量同步和增量同步

全量复制:

  • 首次同步是全量复制,首先从节点向主节点发送 psync 的同步命令,主节点收到后会将自己的 runid 和 offset 发送给从节点,从节点会保存下来,然后主节点会 fork 一个子进程并执行bgsave命令生产 RDB 快照 生成完毕后会发送给从节点,从节点收到后会删除原有的所有数据 然后将收到后的 RDB 文件载入自己的内存
  • 在此期间主节点如果有新写入的数据 会写入到一个缓冲区中,最后主节点再将缓冲区的内容全部发送给从节点,从节点将剩余的数据进行还原

增量复制:

  • 后期内容发生变化时 会进行增量复制,从节点会向主节点发送当前的 offset 位置给主服务器,然后主服务器分析 offset 偏移量 将之后的数据 以及 在缓冲区的积压数据一并发送给从服务器,最后从服务器再保存到其内存中。
  • PS:offset 位置(等同于MySQL的binlog的位置)

其它描述方式:

1) #从服务器连接主服务器,发送PSYNC命令
2) #主服务器接收到PSYNC命令后,开始执行BGSAVE命令生成RDB快照文件并使用缓冲区记录此后执行的所有写命令
3) #主服务器BGSAVE执行完后,向所有从服务器发送RDB快照文件,并在发送期间继续记录被执行的写命令
4) #从服务器收到快照文件后丢弃所有旧数据,载入收到的快照至内存
5) #主服务器快照发送完毕后,开始向从服务器发送缓冲区中的写命令
6) #从服务器完成对快照的载入,开始接受命令请求,并执行来自主服务器缓冲区的写命令
7) #后期同步会先发送自己slave_repl_offset位置,只同步新增加的数据,不再全量同步

Redis 主从复制实现

实现前注意事项

  • 主节点和从节点的配置文件一定要一样,否则会出现问题
  • 一旦搭好主从,从节点则为只读,不能写入数据了(默认是从节点只读 但是也可以修改 replica-read-only)
    • 因为数据流是由 master 向 slave 单向传输,所以slave修改数据的话会导致数据差错
  • 主节点重启后会导致run id改变,会导致触发全量复制
  • 主节点一定要开启持久化,防止数据丢失
  • redis slave也要开启持久化并设置和master同样的连接密码,因为后期slave会有提升为master的可能,slave端切换master同步后会丢失之前的所有数据,而通过持久化可以恢复数据
  • 一旦某个slave成为一个master的slave,redis slave服务会清空当前redis服务器上的所有数据并将master的数据导入到自己的内存中,但是如果只是断开同步关系后,则不会删除当前已经同步过的数据

主节点配置

  • 正常无需配置,但如果使用哨兵功能一定要开启,因为不配置的话当主节点宕机后恢复会因为事先没有配置主从复制而导致无法向新的主节点进行复制
[root@centos8 ~]#vim /etc/redis.conf
replicaof <masterip> <masterport> #指向被复制redis主节点的IP、端口(本机)
masterauth <masterpass>           #指向被复制redis主节点的密码(本机)

从节点配置

#临时生效
127.0.0.1:6379> REPLICAOF master_ip port
127.0.0.1:6379> CONFIG GET masterauth <masterpass>

#永久生效
[root@centos8 ~]#vim /etc/redis.conf
replicaof <masterip> <masterport> #指向被复制redis主节点的IP、端口
masterauth <masterpass>           #指向被复制redis主节点的密码
requirepass password              #主节点的登录密码(为了以后slave成为master没有密码而无法进行复制等问题)

Redis 主从复制优化

[root@centos8 ~]#vim /etc/redis.conf
#master的写入数据缓冲区,用于记录自上一次同步后到下一次同步过程中间的写入命令
#默认只有1MB,生产中要设置的足够大,否则缓冲区过小会导致复制数据不完整
#计算公式:repl-backlog-size = 允许从节点最大中断时长 * 主实例offset每秒写入量,比如master每秒最大写入64mb,最大允许60秒,那么就要设置为64mb*60秒=3840MB(3.8G),建议此值设的足够大
repl-backlog-size 1mb

#redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列,默认360秒,值为0 则表示永远不释放这部分内存
repl-backlog-ttl 3600

#是否使用无盘同步RDB文件,默认为no,no为不使用无盘,需要将RDB文件保存到磁盘后再发送给slave,yes为支持无盘,支持无盘就是RDB文件不需要保存至本地磁盘,而是直接通过socket文件发送给slave
repl-diskless-sync no

#diskless时复制的服务器等待的延迟时间
repl-diskless-sync-delay 5

#slave端向server端发送ping的时间区间设置,默认为10秒(探测对方的状态的间隔时间)
repl-ping-replica-period 10

#设置超时时间,默认60秒
repl-timeout 60

#是否启用tcp-nodelay,如设置成yes,则redis会合并小的TCP包从而节省带宽,但会增加同步延迟(40ms),造成master与slave数据不一致,假如设置成no,则redis会立即发送同步数据,没有延迟,yes关注性能,no关注redis服务中的数据一致性
repl-disable-tcp-nodelay no


#slave端的优先级设置,值是一个整数,数字越小表示优先级越高。当master故障时将会按照优先级来选择slave端进行恢复,如果值设置为0,则表示该slave永远不会被选择
replica-priority 100

#设置一个master的可用slave不能少于多少个,否则master无法执行写
min-replicas-to-write 3

#设置至少由上面数量的slave延迟时间都大于多少秒时,master不接受写操作(拒绝写入)
min-replicas-max-lag 10

删除主从同步

  • REPLIATOF NO ONE 指令可以取消主从复制
#取消复制,在slave上执行REPLIATOF NO ONE,会断开和master的连接 从而不再进行主从复制,但不会清除slave上已有的数据
127.0.0.1:6379> REPLIATOF NO ONE

Redis 级联复制

Redis 级联复制概述

  • 一主一从 从在带一个或多个从

Redis 级联复制实现

  • 只需在一主一从的基础上,将新的从节点配置文件IP端口等指向旧的从节点即可

  • [root@centos8 ~]#vim /etc/redis.conf
    replicaof <masterip> <masterport> #指向旧的从节点的IP、端口
    masterauth <masterpass>           #指向旧的从节点的密码

主节点宕机 从节点手动提升为主

  • 自动提升需要用到redis的哨兵,生产中都是配合哨兵
#在要提升成主节点的从节点上执行
127.0.0.1:6379> REPLICAOF no one #旧版本使用SLAVEOF no one

#在其他的从节点上修改配置文件指向新的从节点ip以及端口号和密码

Redis 哨兵 (sentinel)

Redis 哨兵概述

  • 哨兵可以实现主节点宕机,自动提升一个从节点为新主,从而实现redis的高可用,类似MySQL中的MHA
  • 一个哨兵可以监视多组主从

sentinel状态下应用程序如何连接redis:

  • 客户端会订阅 sentinel 相关的频道,通过频道来获取到集群中主节点,然后直接访问主节点

  • 将所有的sentinel节点放到客户端代码中(为了遍历sentinel节点集合 获取一个可用的sentinel节点以及masterName),在由这个可用的sentinel通过masterName获取master节点,sentinel发送role指令确认master的信息,客户端订阅sentinel的相关频道,获取新的master信息变化,并自动连接新的master

范例:Java客户端连接哨兵模式(只需要配置哨兵节点即可)

spring.redis.sentinel.master=mymaster #哨兵配置中集群名字 
spring.redis.sentinel.nodes=哨兵ip1:哨兵端口1,哨兵ip2:哨兵端口2,哨兵ip3:哨兵端口3

Redis 哨兵工作原理

  • sentinel 搭建完成后 会定时执行三个计划任务来对集群中的各节点(redis、sentinel)进行监控
  • 第一个定时任务:每隔10秒,每个 sentinel 对 master 和 slave 执行 info
    • 主要是发现 slave 节点;确认主从关系;因为 sentinel 可以在配置文件中获取到 master 而不知道 slave的信息
    • 当Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次。作用:发现最新的集群拓扑结构
  • 第二个定时任务:每隔2秒,每个 sentinel 通过 master 节点的 channel 交换对 master 节点的判断信息和自身信息(基于发布/订阅 pub/sub))
  • 第三个定时任务:每隔1秒,每个 sentinel 向其他 sentinel节点 和 redis主从节点 执行ping 来进行心跳检测
    • 如果 master 节点回复 PING 命令的时间超过配置文件中设定的阈值(down-after-milliseconds,默认30s),那么这个 master 会被 sentinel 标记为主观DOWN,然后会询问其他 sentinel 是否同意该 master 下线,当认为该 master 已经故障的 sentinel 节点数超过法定人数半数时,就会认为此 master 已经不可用 并将其标记为客观DOWN
    • 之后 sentinel 会进行内部选举,选举出一个 leader sentinel 来进行故障转移,故障转移就是选择一个合适的 slave 来充当 master,而是否成为 master 是根据配置文件中的replica-priority优先级来判断 该值越小表示优先级越高,如果优先级一致 则判断各slave节点的offset偏移量 offset 值越高就表示向master同步的数据越多 然后会选择同步数据最高的,如果优先级和偏移量都一致 那么就会选择runid最小的slave来成为新的master
    • 最后 leader sentinel 会将被选举出来的 slave 提升为 master,然后将其它的 slave 指向新的 master,如果宕掉的 master 恢复 则会将其变成 slave 并将 master 指向新的 master

Redis 哨兵实现

实现哨兵前注意事项

  • 实现 Sentinel 的前提是已经实现了一个redis主从复制的运行环境

  • master 的配置文件中 masterauth 和 slave 都必须相同

  • Sentinel 点最好大于等于三个 并且为奇数 如:3、5、7…(防止脑裂)

  • 客户端初始化时连接的是 Sentinel 节点集合,不再是具体的 Redis 节点,Sentinel 只是配置中心而非代理

  • Sentinel 节点与普通 Redis 没有区别

  • 读写分离是由客户端写程序决定的

  • master 宕机后哪个slave被提升为新的主节点是根据每个slave设置的优先级决定的,优先级越高 则被提升为master的概率就越大:

  • [root@8 ~]# grep replica-priority /apps/redis/etc/redis.conf 
    replica-priority 100
    #slave端的优先级设置,值是一个整数,数字越小表示优先级越高。当master故障时将会按照优先级来选择slave端进行恢复,如果值设置为0,则表示该slave永远不会被选择

环境说明

10.0.0.8  # master
10.0.0.18 # slave
10.0.0.28 # slave

搭建主从

  • 略(主要是每个主从节点配置要相同,并且都要配置主从,否则当宕机的主节点启动后将无法向新的主节点进行复制)

配置 sentinel

  • sentinel实际上是一个特殊的redis服务器,有些redis指令支持,但很多指令并不支持
  • 默认监听在 26379/tcp 端口
  • 哨兵可以不和redis服务器部署在一起,但一般部署在一起

redis-sentinel.conf

  • 所有 redis 节点使用相同的配置文件 /etc/redis-sentinel.conf(如果是编译安装,在源码目录有sentinel.conf,复制到安装目录即可,要注意权限问题)
  • 必须配置项
bind 0.0.0.0

port 26379

daemonize yes # no表示守护进程前台运行

pidfile "/var/run/redis-sentinel.pid"

requirepass 321 #给 sentinel 也设置一个密码(测试发现设置密码后也可以无需密码登录sentinel?)

dir "/tmp" #工作目录

sentinel monitor mymaster 10.0.0.8 6379 2 
# mymaster 表示当前监控的主从集群的名称
# 10.0.0.8 6379 表示当前主从集群中主节点的IP以及端口号,但主节点宕机此IP地址会自动修改
# 2 为法定人数限制(quorum),即有几个sentinel认为master down了就进行故障转移,一般此值是所有sentinel节点(一般总数是>=3的 奇数,如:3,5,7等)的一半以上的整数值,比如,总数是3,即3/2=1.5,取整为2,是master的ODOWN客观下线的依据

sentinel auth-pass mymaster <password>
#mymaster集群中主节点的密码,要注意此行要在上面行的下面
  • 可选配置项(建议优化项)
sentinel down-after-milliseconds mymaster 3000
#(SDOWN)判断mymaster集群中所有节点的主管下线的时间,单位:毫秒,建议3000(3000毫秒就是3秒,即3秒检测一次master的状态,30秒一检查太慢了)

sentinel parallel-syncs mymaster 1
#发生故障转移后,同时向新master同步数据的slave数量,数字越小同步时间越长,但可以减轻新master的负载压力

sentinel failover-timeout mymaster 180000
#所有slave指向新的master所需的超时时间,单位:毫秒(180000毫秒=180秒)

sentinel deny-scripts-reconfig yes #禁止修改脚本,默认值即可

logfile /var/log/redis/sentinel.log #sentinel日志存放路径

redis-sentinel.service

  • 编译安装没有,需要手动配置
  • /lib/systemd/system/redis-sentinel.service
[Unit]
Description=Redis Sentinel
After=network.target

[Service]
ExecStart=/usr/bin/redis-sentinel /apps/redis/etc/sentinel.conf --supervised systemd
ExecStop=/usr/libexec/redis-shutdown sentinel
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

redis-shutdown

  • 准备停止 sentinel 脚本(测试发现没有停止脚本也可以stop停止 sentinel 服务)
  • /usr/libexec/redis-shutdown
  • 需要给脚本添加执行权限 chmod +x /usr/libexec/redis-shutdown
#!/bin/bash
# Wrapper to close properly redis and sentinel
test x"$REDIS_DEBUG" != x && set -x

REDIS_CLI=/usr/bin/redis-cli

# Retrieve service name
SERVICE_NAME="$1"
if [ -z "$SERVICE_NAME" ]; then
   SERVICE_NAME=redis
fi

# Get the proper config file based on service name
CONFIG_FILE="/apps/redis/etc/$SERVICE_NAME.conf"

# Use awk to retrieve host, port from config file
HOST=`awk '/^[[:blank:]]*bind/ { print $2 }' $CONFIG_FILE | tail -n1`
PORT=`awk '/^[[:blank:]]*port/ { print $2 }' $CONFIG_FILE | tail -n1`
PASS=`awk '/^[[:blank:]]*requirepass/ { print $2 }' $CONFIG_FILE | tail -n1`
SOCK=`awk '/^[[:blank:]]*unixsocket\s/ { print $2 }' $CONFIG_FILE | tail -n1`

# Just in case, use default host, port
HOST=${HOST:-127.0.0.1}
if [ "$SERVICE_NAME" = redis ]; then
    PORT=${PORT:-6379}
else
    PORT=${PORT:-26739}
fi

# Setup additional parameters
# e.g password-protected redis instances
[ -z "$PASS"  ] || ADDITIONAL_PARAMS="-a $PASS"

# shutdown the service properly
if [ -e "$SOCK" ] ; then
	$REDIS_CLI -s $SOCK $ADDITIONAL_PARAMS shutdown
else
	$REDIS_CLI -h $HOST -p $PORT $ADDITIONAL_PARAMS shutdown
fi

启动 sentinel

  • 启动所有节点的sentinel
systemctl enable --now redis-sentinel.service

#启动后观察26379端口是否开启
验证 sentinel 启动状态
redis-cli -a 12345 -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
#注意slave和sentinels数量是否正确
master0:name=mymaster,status=ok,address=10.0.0.8:6379,slaves=2,sentinels=3

---------------------------------------------------------------------------
#配置文件状态,以下内容为开启 sentinel 后自动生成

#master
[root@8 ~]# cat/apps/redis/etc/sentinel.conf
# Generated by CONFIG REWRITE
protected-mode no
supervised systemd
user default on nopass sanitize-payload ~* &* +@all
sentinel myid 4ce643e22d2e12fd94b189717349827166787c09
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel current-epoch 0
sentinel known-replica mymaster 10.0.0.28 6379
sentinel known-replica mymaster 10.0.0.18 6379
sentinel known-sentinel mymaster 10.0.0.28 26379 aa195880b7c55ef982565dabc3d856c09fd61bd4
sentinel known-sentinel mymaster 10.0.0.18 26379 4a8819670c303e1b87a453330cd57b94d7f2b58d

#slave
[root@18 ~]# cat /apps/redis/etc/sentinel.conf
...
# Generated by CONFIG REWRITE
protected-mode no
supervised systemd
user default on nopass sanitize-payload ~* &* +@all
sentinel myid 4a8819670c303e1b87a453330cd57b94d7f2b58d
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel current-epoch 0
sentinel known-replica mymaster 10.0.0.28 6379
sentinel known-replica mymaster 10.0.0.18 6379
sentinel known-sentinel mymaster 10.0.0.28 26379 aa195880b7c55ef982565dabc3d856c09fd61bd4
sentinel known-sentinel mymaster 10.0.0.8 26379 4ce643e22d2e12fd94b189717349827166787c09

#slave
[root@28 ~]# cat /apps/redis/etc/sentinel.conf
...
# Generated by CONFIG REWRITE
protected-mode no
supervised systemd
user default on nopass ~* &* +@all
sentinel myid aa195880b7c55ef982565dabc3d856c09fd61bd4
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel current-epoch 0
sentinel known-replica mymaster 10.0.0.18 6379
sentinel known-replica mymaster 10.0.0.28 6379
sentinel known-sentinel mymaster 10.0.0.8 26379 4ce643e22d2e12fd94b189717349827166787c09
sentinel known-sentinel mymaster 10.0.0.18 26379 4a8819670c303e1b87a453330cd57b94d7f2b58d

测试 sentinel

查看正常状态
[root@8 ~]# redis-cli -a 321 --no-auth-warning info replication
# Replication
role:master
connected_slaves:2
slave0:ip=10.0.0.28,port=6379,state=online,offset=367001,lag=0
slave1:ip=10.0.0.18,port=6379,state=online,offset=367001,lag=0
master_failover_state:no-failover
master_replid:e16b91f94f484c8f5781e489fa4fa81ddfd03106
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:367001
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:367001

[root@18 ~]# redis-cli -a 321 --no-auth-warning info replication
# Replication
role:slave
master_host:10.0.0.8
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_read_repl_offset:388880
slave_repl_offset:388880
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:e16b91f94f484c8f5781e489fa4fa81ddfd03106
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:388880
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:155
repl_backlog_histlen:388726

[root@28 ~]# redis-cli -a 321 --no-auth-warning info replication
# Replication
role:slave
master_host:10.0.0.8
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:394175
slave_repl_offset:394175
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:e16b91f94f484c8f5781e489fa4fa81ddfd03106
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:394175
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:394175
停止master
[root@8 ~]# systemctl stop redis.service
停止master后查看各节点状态
[root@8 ~]# redis-cli -a 321 --no-auth-warning info
Could not connect to Redis at 127.0.0.1:6379: Connection refused

[root@18 ~]# redis-cli -a 321 --no-auth-warning info replication
# Replication
role:master #可以看到10.0.0.18成为了新主
connected_slaves:1
slave0:ip=10.0.0.28,port=6379,state=online,offset=418025,lag=0
master_failover_state:no-failover
master_replid:ce898d464b748d965a74f1e703e8be9bcf718ea2
master_replid2:e16b91f94f484c8f5781e489fa4fa81ddfd03106
master_repl_offset:418025
second_repl_offset:403989
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:155
repl_backlog_histlen:417871

[root@28 ~]# redis-cli -a 321 --no-auth-warning info replication
# Replication
role:slave
master_host:10.0.0.18 #10.0.0.28也自动将主节点设为新主10.0.0.18
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:419764
slave_repl_offset:419764
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:ce898d464b748d965a74f1e703e8be9bcf718ea2
master_replid2:e16b91f94f484c8f5781e489fa4fa81ddfd03106
master_repl_offset:419764
second_repl_offset:403989
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:419764
恢复master
[root@8 ~]# systemctl start redis.service
查看恢复后的状态
[root@8 ~]# redis-cli -a 321 --no-auth-warning info replication
# Replication
role:slave #以前的主节点成为了从节点
master_host:10.0.0.18 #指向了新的主节点
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_read_repl_offset:1
slave_repl_offset:1
master_link_down_since_seconds:-1
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:be03d9fcb2b26fc8e38cbf16fc88fa2393e0091a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
测试恢复后的数据写入情况
#新的主节点写入数据
[root@18 ~]# redis-cli -a 321 --no-auth-warning set testkey 666
OK

#从节点查看
[root@8 ~]# redis-cli -a 321 --no-auth-warning get testkey
"666"
[root@28 ~]# redis-cli -a 321 --no-auth-warning get testkey
"666"
配置文件也会自动修改
[root@8 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
masterauth 321
replicaof 10.0.0.18 6379

[root@18 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
masterauth "321"

[root@28 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
replicaof 10.0.0.18 6379
masterauth "321"

自动切换主从,选举新的主节点

  • 手动触发
redis-cli -p 26379 sentinel failover <masterName>

注意事项

[root@8 ~]# grep replica-priority /apps/redis/etc/redis.conf 
replica-priority 100
#slave端的优先级设置,值是一个整数,数字越小表示优先级越高。当master故障时将会按照优先级来选择slave端进行恢复,如果值设置为0,则表示该slave永远不会被选择

范例

切换前

[root@8 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
masterauth 321
replicaof 10.0.0.18 6379

[root@18 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
masterauth "321"

[root@28 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
replicaof 10.0.0.18 6379
masterauth "321"

切换

  • 在哪个节点切换都可以
redis-cli -p 26379 sentinel failover mymaster

切换后

  • 10.0.0.28被切换成新主
  • 切换有几秒钟的延迟
[root@8 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
masterauth "321"
replicaof 10.0.0.28 6379

[root@18 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
masterauth "321"
replicaof 10.0.0.28 6379

[root@28 ~]# grep -E "^(replicaof|masterauth)" /apps/redis/etc/redis.conf
masterauth "321"

[root@28 ~]# redis-cli -a 321 --no-auth-warning set aaa bbb
OK
[root@8 ~]# redis-cli -a 321 --no-auth-warning get aaa
"bbb"
[root@18 ~]# redis-cli -a 321 --no-auth-warning get aaa
"bbb"

Redis cluster

Redis cluster 概述

  • 在哨兵sentinel机制中,可以解决redis高可用问题,即当master故障后可以自动将slave提升为master,从而可以保证redis服务的正常使用,但是无法解决redis单机写入的瓶颈问题,即单机写入性能受限于单机的内存大小、并发数量、网卡速率等因素。

  • redis 3.0版本之后,推出了无中心架构的redis cluster机制,在无中心的redis集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接

Redis cluster 主要特点

  1. 所有redis节点使用(PING机制)互联
  2. 集群中某个节点是否有效,是由整个集群中超半数的节点检测都失效,才能算真正的失效
  3. 客户端不需要proxy即可直接连接redis,应用程序中需要配置有全部的redis服务器IP
  4. redis cluster把所有的redis node平均映射到0-16383个槽位(solt)上,读写需要到指定的redis node上进行操作,因此有多少个redis node相当于redis并发扩展了多少倍,每个redis node承担了16384/N个槽位,槽位没有容量限制,只是一个逻辑上的概念,假设一万个key,1000个槽位,则每个槽位就会被平均分担10个key
  5. redis cluster预先分配16384个(slot)槽位,当需要在redis集群中写入一个key-value的时候,会使用CRC16(key) mod 16384之后的值,决定将key写入至哪一个槽位从而决定写入哪一个redis节点上,从而有效解决单机瓶颈
  6. 集群开启后就只能整体用,而不能单机用了

Redis cluster 工作原理

  • xxx

Redis cluster 部署

前言

  • 下面实现的是 redis5 原生命令手动部署,此方法 redis4 以前不支持,redis6 和以后版本未作部署测试,需要部署 redis6 及后续版本需参阅官方文档

部署流程概述

  • 在所有节点安装redis,并配置开启cluster功能
  • 各个节点执行meet,实现所有节点的相互通信
  • 为各个master节点指派槽位范围
  • 指定各个节点的主从关系

redis cluster 相关命令

# redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          host:port
                 --cluster-search-multiple-owners
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
  reshard        host:port
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

实战案例:利用原生命令手动部署redis cluster

在所有节点安装redis并启动cluster功能

#在所有6个节点上都执行下面的操作
[root@centos8 ~]$yum -y install redis

[root@centos8 ~]$sed -i.bak -e 's/bind 127.0.0.1/bind 0.0.0.0/' -e '/masterauth/a masterauth 123456' -e '/requirepass/a requirepass 123456' -e '/# cluster-enabled yes/a cluster-enabled yes' -e '/# cluster-config-file nodes-6379.conf/a cluster-config-file nodes-6379.conf' -e '/cluster-require-full-coverage yes/c cluster-require-full-coverage no' /etc/redis.conf

[root@centos8 ~]$systemctl enable --now redis

#观察集群16379端口是否已经开启
[root@centos8 ~]$ss -ntl
State          Recv-Q         Send-Q                  Local Address:Port                    Peer Address:Port         
LISTEN         0              128                           0.0.0.0:22                           0.0.0.0:*            
LISTEN         0              100                           127.0.0.1:25                           0.0.0.0:*            
LISTEN         0              128                           0.0.0.0:16379                       0.0.0.0:*            
LISTEN         0              128                           0.0.0.0:6379                         0.0.0.0:*  

#观察集群进程是否已经开启
[root@centos8 ~]$ps aux|grep redis
redis       2496  0.1  1.0  53520 10064 ?        Ssl  21:07   0:00 /usr/bin/redis-server 0.0.0.0:6379 [cluster]

执行meet操作实现互相通信

#在任一节点上和其他所有节点进行meet通讯
[root@centos8 ~]$for i in `seq 5`;do redis-cli -h 10.0.0.8 -a 123456 --no-auth-warning cluster meet 10.0.0.${i}8 6379;done

#可以看到所有节点之间可以相互连接通讯
[root@centos8 ~]$redis-cli -h 10.0.0.8 -a 123456 --no-auth-warning cluster nodes
fee835a156cc6c8e1e51054b6761b487b8294e4a 10.0.0.58:6379@16379 master - 0 1627653435000 0 connected
71bffbdbc05fd3bbcad3275eaedfe2aabf1c4463 10.0.0.28:6379@16379 master - 0 1627653437000 4 connected
244c8c7479240af1439f91cab541df5042ede03e 10.0.0.8:6379@16379 myself,master - 0 1627653435000 1 connected
7acb1dbf3052c553630610d70aa73776ea87805d 10.0.0.18:6379@16379 master - 0 1627653437231 2 connected
284b159005ae7434fc0ae6071de90be6ad337de1 10.0.0.38:6379@16379 master - 0 1627653438250 5 connected
b16c774f9f86d157048e47e9476f237efc0ed6d3 10.0.0.48:6379@16379 master - 0 1627653435184 3 connected

#由于没有分配槽位所有无法创建key
[root@centos8 ~]$redis-cli -h 10.0.0.8 -a 123456 --no-auth-warning set name xiang
(error) CLUSTERDOWN Hash slot not served

#查看当前集群状态
[root@centos8 ~]$redis-cli -h 10.0.0.8 -a 123456 --no-auth-warning cluster info
cluster_state:fail #状态为失败
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6 #6个节点
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:1
cluster_stats_messages_ping_sent:349
cluster_stats_messages_pong_sent:385
cluster_stats_messages_meet_sent:5
cluster_stats_messages_sent:739
cluster_stats_messages_ping_received:385
cluster_stats_messages_pong_received:354
cluster_stats_messages_received:739


# redis-cli -a 123456 CLUSTER HELP
 1) CLUSTER <subcommand> arg arg ... arg. Subcommands are:
 2) ADDSLOTS <slot> [slot ...] -- Assign slots to current node.
 3) BUMPEPOCH -- Advance the cluster config epoch.
 4) COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.
 5) COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.
 6) DELSLOTS <slot> [slot ...] -- Delete slots information from current node.
 7) FAILOVER [force|takeover] -- Promote current replica node to being a master.
 8) FORGET <node-id> -- Remove a node from the cluster.
 9) GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.
10) FLUSHSLOTS -- Delete current node own slots information.
11) INFO - Return onformation about the cluster.
12) KEYSLOT <key> -- Return the hash slot for <key>.
13) MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.
14) MYID -- Return the node id.
15) NODES -- Return cluster configuration seen by node. Output format:
16)     <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>
17) REPLICATE <node-id> -- Configure current node as replica to <node-id>.
18) RESET [hard|soft] -- Reset current node (default: soft).
19) SET-config-epoch <epoch> - Set config epoch of current node.
20) SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.
21) REPLICAS <node-id> -- Return <node-id> replicas.
22) SLOTS -- Return information about slots range mappings. Each range is made of:
23)     start, end, master and replicas IP addresses, ports and ids

启动redis cluster配置

所有6台主机都执行以下配置

[root@centos8 ~]$dnf -y install redis
  • 每个节点修改redis配置,必须开启cluster功能的参数
#交互式修改
[root@redis-node1 ~]$vim /etc/redis.conf
bind 0.0.0.0
masterauth 123456 #建议配置,否则后期的master和slave主从复制无法成功,还需再配置
requirepass 123456
cluster-enabled yes #取消此行注释,必须开启集群,开启后redis 进程会有cluster显示,还会开启TCP的16379端口
cluster-config-file nodes-6379.conf #取消此行注释,此为集群状态文件,记录主从关系及slot范围信息,由redis cluster 集群自动创建和维护
cluster-require-full-coverage no #默认值为yes,设置为no可以避免一个节点宕机而导致整个cluster不可用

#或非交互方式修改
[root@centos8 ~]$sed -i.bak -e 's/bind 127.0.0.1/bind 0.0.0.0/' -e '/masterauth/a masterauth 123456' -e '/requirepass/a requirepass 123456' -e '/# cluster-enabled yes/a cluster-enabled yes' -e '/# cluster-config-file nodes-6379.conf/a cluster-config-file nodes-6379.conf' -e '/cluster-require-full-coverage yes/c cluster-require-full-coverage no' /etc/redis.conf

#启动redis服务
[root@redis-node1 ~]$systemctl enable --now redis

#查看服务状态
[root@redis-node1 ~]$ss -ntl
State          Recv-Q         Send-Q                  Local Address:Port                    Peer Address:Port           
LISTEN         0              128                           0.0.0.0:16379                        0.0.0.0:*            
LISTEN         0              128                           0.0.0.0:6379                         0.0.0.0:*   
[root@redis-node1 ~]$ps aux|grep redis
redis       2625  0.1  1.0  53520 10192 ?        Ssl  19:23   0:00 /usr/bin/redis-server 0.0.0.0:6379 [cluster]

创建集群

#--cluster-replicas 1 表示每个master对应一个slave节点,系统会认为前三个为主节点,后三个为从节点(1-4,2-5,3-6)
#再任意节点上执行以下指令
[root@redis-node1 ~]$redis-cli -a 123456 --cluster create 10.0.0.8:6379 10.0.0.18:6379 10.0.0.28:6379 10.0.0.38:6379 10.0.0.48:6379 10.0.0.58:6379 --cluster-replicas 1
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.0.0.38:6379 to 10.0.0.8:6379
Adding replica 10.0.0.48:6379 to 10.0.0.18:6379
Adding replica 10.0.0.58:6379 to 10.0.0.28:6379
M: e172e59a02c59bc0c4f47d14d499ac2e55e3227e 10.0.0.8:6379
   slots:[0-5460] (5461 slots) master
M: f34da29529ba98e94610904125fbb5b8daf322f8 10.0.0.18:6379
   slots:[5461-10922] (5462 slots) master
M: 15966787360d65805bcf296ac2362f18d0b28261 10.0.0.28:6379
   slots:[10923-16383] (5461 slots) master
S: 349eff08094b52ff528d7fdd1e715844c611b753 10.0.0.38:6379
   replicates e172e59a02c59bc0c4f47d14d499ac2e55e3227e
S: bbd7c7dfc42ed2e310c1cd74cb58a3a588764501 10.0.0.48:6379
   replicates f34da29529ba98e94610904125fbb5b8daf322f8
S: 36a67b32ff67626343831d03c514217e4fc20472 10.0.0.58:6379
   replicates 15966787360d65805bcf296ac2362f18d0b28261
Can I set the above configuration? (type 'yes' to accept): yes #敲yes

确认分配状态

#说明:slave后的编号是对应主节点的编号

[root@redis-node1 ~]$redis-cli -a 123456 cluster nodes
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
f34da29529ba98e94610904125fbb5b8daf322f8 10.0.0.18:6379@16379 master - 0 1627991121892 2 connected 5461-10922
e172e59a02c59bc0c4f47d14d499ac2e55e3227e 10.0.0.8:6379@16379 myself,master - 0 1627991120000 1 connected 0-5460
349eff08094b52ff528d7fdd1e715844c611b753 10.0.0.38:6379@16379 slave e172e59a02c59bc0c4f47d14d499ac2e55e3227e 0 1627991119856 4 connected
15966787360d65805bcf296ac2362f18d0b28261 10.0.0.28:6379@16379 master - 0 1627991120872 3 connected 10923-16383
36a67b32ff67626343831d03c514217e4fc20472 10.0.0.58:6379@16379 slave 15966787360d65805bcf296ac2362f18d0b28261 0 1627991119000 6 connected
bbd7c7dfc42ed2e310c1cd74cb58a3a588764501 10.0.0.48:6379@16379 slave f34da29529ba98e94610904125fbb5b8daf322f8 0 1627991119000 5 connected
[root@redis-node1 ~]$redis-cli -a 123456 cluster info
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6 #6个主从节点
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:406
cluster_stats_messages_pong_sent:398
cluster_stats_messages_sent:804
cluster_stats_messages_ping_received:393
cluster_stats_messages_pong_received:406
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:804
#连接到任意节点都可以查看到此信息
[root@redis-node1 ~]$redis-cli -a 123456 --cluster info 10.0.0.8:6379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
10.0.0.8:6379 (e172e59a...) -> 0 keys | 5461 slots | 1 slaves.
10.0.0.18:6379 (f34da295...) -> 0 keys | 5462 slots | 1 slaves.
10.0.0.28:6379 (15966787...) -> 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.

查看key对应的槽位

redis-cli -h 10.0.0.8 -a 123456 --no-auth-warning cluster keyslot keyname #keyname为key的名字

Redis cluster 后期维护

redis集群运行之后,难免由于硬件故障,网络规划、业务增长等原因对已有集群进行相应的调整,比如:增加redis node节点、减少节点、节点迁移、更换服务器等。增加节点和删除节点会涉及到已有的槽位重新分配及数据迁移。

集群维护之动态扩容

实战案例:

因公司业务发展迅猛,现有的三主三从的redis cluster架构可能无法满足现有业务的并发写入需求,因此公司紧急采购两台服务器10.0.0.68,10.0.0.78,需要将其动态添加到集群中,但不能影响业务的使用和数据丢失

注意:生产环境master节点为奇数个,如:3 5 7 等,以防脑裂现象发生

添加节点准备

增加redis node节点,要与之前的redis node节点 版本、配置相同,然后分别再启动两台redis node,应为一主一从。

添加新的master节点到集群

使用以下命令添加新节点,要添加的新redis节点IP和端口添加到的已有的集群中任意节点的IP:端口

redis5添加方式
#将一台新的主机10.0.0.68加入集群,以下示例中10.0.0.58可以是任意存在的集群节点
[root@aliyun ~]# redis-cli -a 123456 --cluster add-node 10.0.0.68:6379 <当前集群任意节点>:6379
#执行完毕后 10.0.0.68:6379 会成为主节点

在新的master上重新分配槽位

新的node节点加到集群之后,默认是master节点,但是没有slots,需要重新分配

添加主机之后需要对添加至集群中的新主机重新分片,否则没有分片也就无法写入数据

注意:重新分配槽位需要清空数据,所以需要先备份数据,扩展后再进行数据恢复

redis5:
[root@aliyun ~]# redis-cli -a 123456 --cluster reshard <当前任意集群节点>:6379

#输入总槽位数/主节点个数的值
#输入all,表示从每个主节点的槽位中移动出一部分分配给新的主节点
#输入yes

为新的master添加新的slave节点

方法一:在新加节点到集群时,直接将之设置为slave

redis5添加方式:

redis-cli -a 123456 --cluster add-node 10.0.0.78:6379 <任意集群节点>:6379 --cluster-slave --cluster-master-id o21j3o213n21oi3noi12321us123021csd
方法二:在添加节点到集群,添加后默认为主,在将其设置为从

redis5添加方式:

#添加后默认为主
[root@aliyun ~]# redis-cli -a 123456 --cluster add-node 10.0.0.78:6379 <当前集群任意节点>:6379

#更改为从
[root@aliyun ~]# redis-cli -h 10.0.0.78 -p 6379 -a 123456 #登录到新添加节点
10.0.0.78:6379> CLUSTER NODES #查看当前集群节点,找到目标master的ID
10.0.0.78:6379> CLUSTER REPLICATE <master-id> #添加

集群维护之动态缩容

实战案例:

由于10.0.0.8服务器使用年限已超三年,超过厂商质保期,而且硬盘出现异常报警,经运维部架构师提交方案并同开发同事开会商议,决定将现有的redis集群的8台主服务器中的master 10.0.0.8和对应的slave 10.0.0.38临时下线,三组服务器的并发写入性能足够支撑未来1-2年的业务需求

删除节点过程:

添加节点是先将node节点添加到集群,然后分配槽位,而删除节点和添加正好相反,需要先将被删除的节点中的数据备份,然后再将数据清空,最后将被删除节点槽位分配给其他节点,才能进行删除,否则将会提示节点中有数据或者槽位 从而无法进行删除

将要被删除的节点的剩余槽位分配给其他主节点

[root@aliyun ~]# redis-cli -a 123456 --cluster reshard <当前任意集群节点>:6379

#slots=(被删除的节点的剩余槽位数/剩余主节点的个数)
#接受此槽位的主节点id
#源槽位的id
#done
#yes


#有几个剩余主节点就重复几次
[root@aliyun ~]# redis-cli -a 123456 --cluster reshard <当前任意集群节点>:6379
...

从集群中删除服务器

[root@aliyun ~]# redis-cli -a 123456 --cluster del-node <当前任意集群节点IP>:6379 <删除的集群节点ID>

#在删除的节点上再删除配置文件
[root@aliyun ~]# rm -f /var/lib/redis/nodes-6379.conf

将redis服务器迁移到新建的Redis cluster

工作中还是要让开发写程序将key导出,再将数据迁移到redis集群中

集群偏斜

redis cluster 多个节点运行一段时间后,可能会出现倾斜现象,即某个节点数据偏多,内存消耗更大,或者接受用户请求访问更多

发生倾斜的原因可能如下:

  • 节点和槽位分配不均
  • 不同槽位对应键值数量差异较大
  • 包含bigkey,建议少用
  • 内存相关配置不一致
  • 热点数据不均衡:一致性不高时,可以使用本缓存和MQ

Redis cluster的局限性

  • 大多数时客户端性能会“降低”
  • 命令无法跨界点使用:mget、keys、scan、flush、sinter等
  • 客户端维护更复杂:SDK和应用本身消耗(例如更多的连接池)
  • 不支持多个数据库:集群模式下只有一个 db0
  • 复制只支持一层:不支持树形复制结构,不支持级联复制
  • key事务和Lua支持有限:操作的key必须在一个节点,Lua和事务无法跨节点使用

集群相关配置

[root@aliyun ~]# vim /etc/redis.conf

cluster-require-full-coverage yes #如果三组主从中,有一组主从宕机,其他的两组主从中的槽位数据还能否被访问,yes表示不能访问,no表示可以继续访问

cluster-enabled yes #是否启用集群yes表示启用,no表示禁用