上一章中简单了实现了一个主从复制使的Redis实现了读写分离。通过实现主从复制使得数据安全有了保障,且读写分离提高了Redis的整体性能。但是Redis并不能完美解决主服务宕机后,整体服务的不可用。
上一章的例子中,假如主服务宕机后,我们需要手动把一台从服务器切换成主服务,这个动作全程需要人工干预。
中间可能发生操作失误,且在操作过程中会导致一段时间内服务不可用。为了解决上面的问题,我们需要引入一套自动化的解决方案,那就是哨兵机制。
哨兵模式是Redis针对上述问题,提供的一套自动化解决方案的模式。其具体原理是配置哨兵通过命令从而监控多个Redis实例。通过命令请求,实时获取Redis服务其运行状态,以及其主从服务器的拓扑关系。当哨兵确认主服务宕机会根据选举将某个从服务切换成主服务,然后通过发布订阅模式通知其他从服务,修改配置,切换主机。
整个关系可以用下面的图描述
哨兵对数据的监控
哨兵间的监控
主服务宕机
is-master-down-by-addr
命令,需要其他哨兵协助判断该主服务状态。Sentinel never starts a failover if the majority of Sentinel processes are unable to talk
根据上面官方文档中的描述,在一半以上哨兵不可用的时候,故障转移不会启动。
所以这也是我们一般要求配置3个及以上的哨兵,只配置1个或2个哨兵,当哨兵宕机的时候,启动故障转移则无法启动。
这里介绍单机配置主从复制,一主两从
配置Redis客户端
将Redis客户端复制三分,分别为redis、redis-slave1、redis-slave2
修改两个从服务配置
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6380
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6381
requirepass
,所以从服务都需要配置主服务的密码(注意之前配置主从的时候并不会给主服务配置masterauth。但是为了方便主服务宕机后重新加入服务,所以主从都使用了一样的密码配置,而哨兵虽然会修改slaveof,但并不会修改masterauth,所以主服务需要添加masterauth)# 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
slaveof 127.0.0.1 6379
# 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
masterauth 123456
redis-server ../redis.conf
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381
到这一步,我们赢已经有一个主二从的集群了。现在我们需要为这个主从复制的集群设置哨兵。
现在将Redis原始的客户端复制三分,配置3个哨兵,分别为redis-sentinel1、redis-sentinel2、redis-sentinel3。
每个哨兵需要修改各自的端口,以及监听的主服务器。在Redis安装目录下有一个sentinel.conf
文件针对哨兵的配置修改在文件内。
每个哨兵配置不同的启动端口。
哨兵 | 端口 |
---|---|
redis-sentinel1 | port 26379 |
redis-sentinel2 | port 26380 |
redis-sentinel3 | port 26381 |
针对主服务的配置,每个哨兵的配置都是一样的。
# 配置监听的主服务器,monitor代表监控,mymaster代表服务器的名称,可以自定义,127.0.0.1代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass
sentinel auth-pass mymaster 123456
然后在哨兵各自src文件下执行下面的命令
redis-sentinel ../sentinel.conf
可以看到三个数据节点以及三个哨兵已经启动
[root@name src]# ps aux|grep redis
root 8024 0.0 0.0 156452 7656 ? Ssl 11:47 0:14 redis-server 127.0.0.1:6379
root 8029 0.0 0.0 156452 7944 ? Ssl 11:47 0:14 redis-server 0.0.0.0:6380
root 8067 0.0 0.0 156452 7876 ? Ssl 11:50 0:14 redis-server 0.0.0.0:6381
root 8072 0.0 0.0 24720 7472 pts/1 S+ 11:51 0:00 redis-cli -p 6380
root 8074 0.0 0.0 24720 7476 pts/2 S+ 11:52 0:00 redis-cli -p 6381
root 8443 0.1 0.0 153892 7604 ? Ssl 16:20 0:00 redis-sentinel *:26379 [sentinel]
root 8448 0.1 0.0 153892 7584 ? Ssl 16:21 0:00 redis-sentinel *:26380 [sentinel]
root 8461 0.0 0.0 153892 7876 ? Ssl 16:23 0:00 redis-sentinel *:26381 [sentinel]
我们连接哨兵去查看现在的主服务
[root@name src]# redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379"
7) "runid"
8) "f9ea22f2431b0e4773aa850db11a15cd20d0e169"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "599"
19) "last-ping-reply"
20) "599"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "3576"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "1288349"
29) "config-epoch"
30) "0"
31) "num-slaves"
32) "2"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
模拟故障
我们直接kill掉主服务。
[root@name src]# ps aux|grep redis
root 8024 0.0 0.0 156452 7744 ? Ssl 11:47 0:16 redis-server 127.0.0.1:6379
root 8029 0.0 0.1 156452 8196 ? Ssl 11:47 0:15 redis-server 0.0.0.0:6380
root 8067 0.0 0.1 156452 8200 ? Ssl 11:50 0:15 redis-server 0.0.0.0:6381
root 8072 0.0 0.0 24720 7472 pts/1 S+ 11:51 0:00 redis-cli -p 6380
root 8074 0.0 0.0 24720 7476 pts/2 S+ 11:52 0:00 redis-cli -p 6381
root 8443 0.1 0.0 153892 7872 ? Ssl 16:20 0:02 redis-sentinel *:26379 [sentinel]
root 8448 0.1 0.0 153892 7896 ? Ssl 16:21 0:01 redis-sentinel *:26380 [sentinel]
root 8461 0.1 0.0 153892 7892 ? Ssl 16:23 0:01 redis-sentinel *:26381 [sentinel]
root 8503 0.0 0.0 112712 980 pts/0 S+ 16:44 0:00 grep --color=auto redis
[root@name src]# kill -9 8024
此时可以看到主服务已经是6380了
[root@name src]# redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6380"
7) "runid"
8) "0405ebd58dc850ef5d59cf69cbe7c4d7a89f902b"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "903"
19) "last-ping-reply"
20) "903"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "9753"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "29924"
29) "config-epoch"
30) "1"
31) "num-slaves"
32) "2"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
此时我们重启6379的实例,然后查看主从关系。会发现6379成为了6380的从服务
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=336185,lag=1
slave1:ip=127.0.0.1,port=6379,state=online,offset=336185,lag=1
master_replid:6d702a86ec544bd86516218bf96903ec5b697e42
master_replid2:61c841bb0fc2c9b708032e39ae21eb4cc1f45ca8
master_repl_offset:336318
second_repl_offset:290704
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:336318
这里我使用了我以前项目作为模板: redis简单使用
spring:
application:
name: sample.redis
redis:
# 使用哨兵模式这两个就可以不使用了
#database: 0
#host: 127.0.0.1
jedis:
pool:
#最大连接数据库连接数,设 0 为没有限制
max-active: 8
#最大等待连接中的数量,设 0 为没有限制
max-idle: 8
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
max-wait: -1ms
#最小等待连接中的数量,设 0 为没有限制
min-idle: 0
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
shutdown-timeout: 1000ms
# 哨兵信息的配置
sentinel:
master: mymaster
nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381
password: 123456
server:
port: 8000
一个用来测试的Controller
package dai.samples.redis.pubsub.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @author daify
* @date 2019-09-01
*/
@RestController
@RequestMapping("test")
public class TestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
*
* @return
*/
@RequestMapping(value = "{key}/{value}",method = RequestMethod.GET)
public String setData(@PathVariable("key") String key,
@PathVariable("value") String value) {
stringRedisTemplate.opsForValue().set(key,value);
String s = stringRedisTemplate.opsForValue().get(key);
return s;
}
}
我们请求: http://localhost:8000/test/one/31 ,可以拿到返回值31。
关闭主服务
现在我们在服务器中kill掉主服务
[root@name src]# ps aux|grep redis
root 8741 0.1 0.0 156452 7696 ? Ssl 18:24 0:03 redis-server 0.0.0.0:6379
root 8749 0.4 0.0 153892 7756 ? Ssl 18:25 0:09 redis-sentinel 0.0.0.0:26379 [sentinel]
root 8759 0.4 0.0 153892 7812 ? Ssl 18:25 0:09 redis-sentinel 0.0.0.0:26381 [sentinel]
root 8822 0.4 0.0 153892 7608 ? Ssl 18:54 0:02 redis-sentinel 0.0.0.0:26380 [sentinel]
root 8835 0.0 0.0 24720 7476 pts/0 S+ 18:55 0:00 redis-cli -p 26379
root 8857 0.2 0.1 162596 9500 ? Ssl 19:00 0:00 redis-server 0.0.0.0:6381
root 8877 0.2 0.0 156452 7704 ? Ssl 19:03 0:00 redis-server 0.0.0.0:6380
root 8882 0.0 0.0 112708 980 pts/1 S+ 19:03 0:00 grep --color=auto redis
[root@name src]# kill -9 8741
[root@name src]# ps aux|grep redis
root 8749 0.4 0.0 153892 7704 ? Ssl 18:25 0:09 redis-sentinel 0.0.0.0:26379 [sentinel]
root 8759 0.4 0.0 153892 7696 ? Ssl 18:25 0:09 redis-sentinel 0.0.0.0:26381 [sentinel]
root 8822 0.4 0.0 153892 7696 ? Ssl 18:54 0:02 redis-sentinel 0.0.0.0:26380 [sentinel]
root 8835 0.0 0.0 24720 7476 pts/0 S+ 18:55 0:00 redis-cli -p 26379
root 8857 0.2 0.1 162596 9616 ? Ssl 19:00 0:00 redis-server 0.0.0.0:6381
root 8877 0.1 0.0 156452 7888 ? Ssl 19:03 0:00 redis-server 0.0.0.0:6380
root 8885 0.0 0.0 112708 976 pts/1 S+ 19:03 0:00 grep --color=auto redis
然后请求:http://localhost:8000/test/two/120 ,可以拿到120的返回值。而这个过程原来的主服务已经发生了变化,并且这个变化全程是自动化的。
可能出现的问题
Caused by: io.lettuce.core.RedisConnectionException: Cannot connect to a Redis Sentinel
本人使用的是阿里云,默认没有打开哨兵所选的端口,只需要在控制台中打开。
因为demo使用的单服务器多Redis实例。最开始使用127.0.0.1 +
Unable to connect to 127.0.0.1:6380
端口方式配置,而测试代码在本地导致获取的IP无法访问,这里需要修改主从配置和哨兵的配置
主从配置,修改为外网访问的地址
# 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
slaveof 127.0.0.1 6379
# 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
masterauth 123456
哨兵配置,修改为外网访问的地址
# 配置监听的主服务器,monitor代表监控,mymaster代表服务器的名称,可以自定义,127.0.0.1代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass
sentinel auth-pass mymaster 123456
NOAUTH Authentication required
io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required
简单的就是设置了密码但是没有提供与之匹配的密码设置。这里需要检查application.yml中的密码是否配置,虽然在哨兵中配置了服务的密码,但是在项目中密码的设置还是需要的。
找不到了slaveof
在发现配置错误之后尝试修改主从配置中的slaveof
但是你可能无法找到之前配置的slaveof
。因为在Redis 5之后的版本中,因为slave有奴隶的含义,被相关人士抗议,作者为了避免奴隶制相关描述。后续修改了slave相关的参数,在启动后对配置重写的时候会将slaveof
替换为replicaof
replicaof 127.0.0.1 6380