在分布式系统架构设计中高可用是必须考虑的因素之一。高可用通常是指,通过设计减少系统不能提供服务的时间。而单点是系统高可用的最大的败笔,如果单点出现问题的话,那么整个服务就不能使用了,所以应该尽量在系统设计的过程中避免单点。对于 redis 服务也是这样,今天我们就来实现 Redis 的高可用的基础 --> 主从配置。
主从概念
有多台 Redis 服务器(至少两台或以上),其中一台是主服务器(master) 负责写指令的操作,其他都是从服务器(slave)负责读指令的操作。
主从服务器之间会进行数据的同步(即:主服务器会将数据同步到从服务器去),保证数据是一致的。从服务器将读指令操作分流减少服务器压力,而且其中一台从服务器出现错误,不影响其他从服务器的使用。
如果是主服务器出现问题,那么就需要实现故障转移(手动/自动)将其中一台从服务器提升为主服务器,其他从服务器都从这个新的主服务器同步数据。
从上面这段话中我们可以知道 Redis 实现高可用的两个功能点:
- 主从复制
- 故障转移
主从复制
主从复制流程
1)从服务器连接主服务器,发送SYNC命令;
2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
4)从服务器收到快照文件后丢弃所有旧数据(如果有的话),载入收到的快照;
5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
7) 主服务器接收到的每一个写指令都向从服务器发送,从服务器接收并执行收到的写命令。
注:主从服务器刚连接的时候,会进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
主从复制实现方式
- slaveof 命令
- 配置文件
主从复制实践
前期准备
1)复制两份 redis-6379.conf
配置文件,分别命名为 redis-7000.conf
和 redis-7001.conf
2) 编辑 redis-7000.conf
和 redis-7001.conf
文件将端口分别修改为 7000 和7001并将日志文件格式以端口命名,以 redis-6379.conf
为例:
# 允许远程访问
## bind 127.0.0.1
protected-mode no
# 服务端口号
port 6379
# 以守护进程启动
daemonize yes
# 日志文件名称
logfile "redis-6379.log"
# 启用日志文件
syslog-enabled yes
3)分别启动 6379,7000 和 7001 Redis 服务
主从复制实践之 slaveof 命令
我们将会让 6379
成为主服务器,7000
和 7001
为从服务器。
1)进入 7000
并将其设置为 6379
的从服务器
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000
127.0.0.1:7000> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:7000> exit
2)查看 6379 信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
输出如下:
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7000,state=online,offset=210,lag=0
master_replid:9fe7d7ca84fe33cf8e916be1ee3a1b0423675ef8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210
2)查看 7000 信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 info replication
输出如下:
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:336
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:9fe7d7ca84fe33cf8e916be1ee3a1b0423675ef8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:336
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:336
可以看到 6379
的 role
是 master
表示为主服务器,而 7000
的 role
是 slave
表示为从服务器,证明我们刚刚的配置成功了。
3)我们尝试在 6379
设置一些值
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379
127.0.0.1:6379> set name MarkLogZhu
OK
127.0.0.1:6379> set age 18
OK
然后进 7000
看看是不是会将数据同步过来(执行此操作前,先将两边的数据都清空)
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000
127.0.0.1:7000> get name
"MarkLogZhu"
127.0.0.1:7000> get age
"18"
可以看到数据同步过来了,我们试试在 7000
上写入下数据
127.0.0.1:7000> set name1 123
(error) READONLY You can't write against a read only slave.
可以看到从服务器上不能执行写指令操作的。
- 启动
7001
并将其也设置为6379
的从服务器,看看是否也会同步数据过来
127.0.0.1:7001> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:7001> get name
"MarkLogZhu"
127.0.0.1:7001> get age
"18"
可以看到会将 6379
之前的数据也同步过来。
- 在
7000
上将主从绑定取消
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000
127.0.0.1:7000> slaveof no one
OK
6)查看主服务器信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7001,state=online,offset=70,lag=0
master_replid:b3a6488a9667dd3f80c9549146ed012a4ec1d465
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70
[root@VM_0_15_centos redis4]#
可以看到就一个从服务器,端口号为 7001
,要注意的是 7000
上的数据还是存在的不会主动清空。
主从复制实践之配置文件
1)将 Redis 服务都关闭
[root@VM_0_15_centos redis4]# ps -ef |grep redis
root 4220 1 0 15:10 ? 00:00:00 ./redis-server *:6379
root 4238 1 0 15:11 ? 00:00:00 ./redis-server *:7000
root 4325 1 0 15:13 ? 00:00:00 ./redis-server *:7001
root 4830 1579 0 15:18 pts/0 00:00:00 grep --color=auto redis
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7001 shutdown
- 编辑
redis-7000.conf
增加如下内容:
slaveof 127.0.0.1 6379
- 编辑
redis-7001.conf
增加如下内容:
slaveof 127.0.0.1 6379
- 依次启动服务
[root@VM_0_15_centos redis4]# ./redis-server redis-6379.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7000.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7001.conf
- 查看主从状态
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7000,state=online,offset=84,lag=1
slave1:ip=127.0.0.1,port=7001,state=online,offset=84,lag=0
master_replid:645cc6d0869b23998c1265af1d556e671330d2f0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
可以看到服务一启动就已经自动配置为一主二从了,在实际项目中通过配置文件的形式更常见。
主从故障
什么是主从故障
主从故障就是主服务器出现问题,导致无法执行写命令。我们来演示下主服务器故障看看
- 启动三个服务
[root@VM_0_15_centos redis4]# ./redis-server redis-6379.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7000.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7001.conf
- 将主服务器进程关闭(模拟突然出现问题)
[root@VM_0_15_centos redis4]# ps -ef |grep redis
root 5538 1 0 15:25 ? 00:00:00 ./redis-server *:6379
root 5545 1 0 15:25 ? 00:00:00 ./redis-server *:7000
root 5551 1 0 15:25 ? 00:00:00 ./redis-server *:7001
root 6179 1579 0 15:34 pts/0 00:00:00 grep --color=auto redis
[root@VM_0_15_centos redis4]# kill -9 5538
3)查看主从状态
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:891
master_link_down_since_seconds:216
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:645cc6d0869b23998c1265af1d556e671330d2f0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:891
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:891
可以看到 Redis 是无法自动切换的,只能通过人为执行命令解除主从配置,并将其中一个从服务器提升为主服务器。除了人为来操作之外 Redis 提供了一个 sentinel
(哨兵)来实现故障自动转移。
Sentinel(哨兵)
什么是 sentinel
sentinel
就相当于是一个哨兵,当 Redis
主服务器出现故障的时候,代替我们人为的将其中一个服务器提升为主服务器,当主服务器修复故障后自动降格为从服务器加入进来。
多个哨兵节点(至少三个,并且是奇数)会使用 Raft算法(选举算法)实现选举机制,选出一个哨兵节点当作领导者来完成转移和通知。
Sentinel 是如何实现故障转移的?
Sentinel 做到故障转移是基于三个定时任务的执行:
- 每隔 10s 每个 sentinel 会对 master 节点和 slave 节点执行 info 命令
作用就是发现 slave 节点,并且确认主从关系,因为 redis-Sentinel 节点启动的时候是知道master节点的,只是没有配置相应的 slave 节点的信息
- 每隔两秒,sentinel 都会通过 master节点内部的 channel 来交换信息(基于发布订阅)
作用是通过master节点的频道来交互每个Sentinel对master节点的判断信息
- 每隔一秒每个 sentinel 对其他的redis节点(master,slave,sentinel)执行ping操作
对于 master 来说若超过 30s 内没有回复,就对该 master进行主观下线并询问其他的 Sentinel节点是否可以客观下线
客观下线和主观下线
- 主观下线:每个Sentinel节点对Redis节点失败的“偏见”
- 客观下线:所有Sentinel节点对Redis节点失败达成共识
在进行领导者选举之后进行故障转移,这个过程由成为 leader 的 Sentinel 的来完成。
Sentinel 的选举流程(Raft算法)
1)每个做出主观下线操作的 Sentinel 节点向其他 Sentinel 节点发送命令,要求成为领导者。
2)收到命令的 Sentinel 节点默认同意第一个收到的请求,其他拒绝
3)当其中一个 Sentinel 节点获取到的票数一次超过 Sentinel 集合半数并且超过配置里的 quorum,那么它将成为领导者。
4)如果此过程中存在多个 Sentinel 节点成为领导者,那么将等待一段时间后重新进行选举。
故障转移
1)从 slave
节点中选出一个 “合适的” 节点作为新的 master
节点
2)对选中的 slave
节点执行 slaveof no one
命令,让其成为 master
节点
3)对剩余的 slave
节点发送命令,让他们成为新的 master
节点的slave
节点,复制规则和 parallel-syncs
参数有关。
4)将原来的 master
节点配置为 slave
,并保持对其的 "关注",当它恢复后令它去复制新的 master
节点。
“合适的” slave 节点
1.选择 slave-priority(slave节点优先级)最高的 slave
节点,如果存在则返回,否则继续。
2.选择复制偏移量最大的 slave
节点(复制的最完整的),如果存在则返回,否则继续。
3.选择 runId
最小的 slave
节点。
实践
1)编辑 sentinel 配置文件
vim sentinel-26379.conf
内容如下:
# 配置 sentinel 端口号
port 26379
# 以守护进程启动
daemonize yes
# 绑定只在本地使用
bind 127.0.0.1
# 日志文件名称
logfile "sentinel_26379.log"
# 日志文件存放地址
dir "./"
# 监控 master 名字叫做 mymaster,地址是 127.0.0.1 端口号是 6379,1 表示有几个 sentinel 认为该 master 出现故障,触发主备切换动作
sentinel monitor mymaster 127.0.0.1 6379 1
# 3000 毫秒没有响应就判断出现故障
sentinel down-after-milliseconds mymaster 30000
# 主备切换时,多少个从服务器同步更新数据,数值越小越好
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
- 启动 sentinel
[root@VM_0_15_centos redis4]# ./redis-sentinel sentinel-26379.conf
3)将主服务器进程关闭(模拟突然出现问题)
[root@VM_0_15_centos redis4]# kill -9 9149
[root@VM_0_15_centos redis4]# ps -ef |grep redis
root 5550 1 0 16:17 ? 00:00:00 ./redis-server *:7000
root 5551 1 0 15:25 ? 00:00:03 ./redis-server *:7001
root 13044 1 0 16:14 ? 00:00:00 ./redis-sentinel 127.0.0.1:26379 [sentinel]
root 13306 1579 0 16:17 pts/0 00:00:00 grep --color=auto redis
- 查看
7000
主从信息
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7001
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:17931
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:0219097153045d031f7bcfdccebd52b336e542e5
master_replid2:66aa7374642058a6bb26c9c0258e3c8b2fc7d760
master_repl_offset:17931
second_repl_offset:12757
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:17931
[root@VM_0_15_centos redis4]#
可以看到现在主服务器是端口 7001
了
5)将 6379
端口服务重新启动,然后查看 7001
主从状态
[root@VM_0_15_centos redis4]# ./redis-cli -p 7001 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7000,state=online,offset=29364,lag=1
slave1:ip=127.0.0.1,port=6379,state=online,offset=29364,lag=0
master_replid:0219097153045d031f7bcfdccebd52b336e542e5
master_replid2:66aa7374642058a6bb26c9c0258e3c8b2fc7d760
master_repl_offset:29364
second_repl_offset:12757
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset
可以看到 6379
端口也变成了一个从服务器添加进来了。要注意的是这个时候配置文件已经被 sentinel
修改了,就算你重启服务,也是按目前的主从来设置,而不是重新以 6379
为主服务器,我们可以来看看:
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7000 shutdown
[root@VM_0_15_centos redis4]# ./redis-cli -p 7001 shutdown
[root@VM_0_15_centos redis4]# ./redis-server redis-6379.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7000.conf
[root@VM_0_15_centos redis4]# ./redis-server redis-7001.conf
[root@VM_0_15_centos redis4]# ./redis-cli -p 6379 info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7001
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:835
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f3ed896873d5a9024a8139dac59b86381e53ad08
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:835
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:835
重启服务后我们可以看到 6379
还是充当从服务器的角色。