主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
概述
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
开启
Redis提供了redis-sentinel
命令,后面跟配置文件即可.也可以使用redis-server /path/to/sentinel.conf --sentinel
配置
Redis提供了redis-sentinel
命令,用来开启哨兵模式,不过单独的redis-sentinel
命令只能使用代码中的默认配置,比如端口是26379
,为了增加选举性,我们需要开启多个哨兵服务,这就涉及到使用指定配置文件来修改端口以及其他配置项了.
################### 基础配置 ###################
# 端口,默认26379
port 26379
# 是否以守护进程开启
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "/var/log/redis/redis-sentinel-26379.log"
dir "/var/soft/redis/data"
# 保护模式,默认关闭
protected-mode no
# 监控谁,设置主节点名称.最后一个参数是当几个哨兵认为主机失联后进行故障转移(重新选举master)
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
################### 其他配置 ###################
# 指定哨兵在监控Redis服务时,当Redis服务在一个默认毫秒数内都无法回答时,单个哨兵认为的主观下线时间,默认为30000(30秒)
sentinel down-after-milliseconds mymaster 30000
# 指定可以有多少个Redis服务同步新的主机,一般而言,这个数字越小同步时间越长,而越大,则对网络资源要求越高
sentinel parallel-syncs 2
# 指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟
sentinel failover-timeout mymaster 180000
# 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,比较常用
sentinel notification-script mymaster shell_script_path
sentinel down-after-milliseconds配置项只是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用。对于其他哨兵而言,并不是这样认为。哨兵会记录这个消息,当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover,此时哨兵会重写Redis的哨兵配置文件,以适应新场景的需要。
API
开启哨兵模式后,可以使用自带API查看各种状态
查看方式比较简单,使用redis-cli -p sentinel_port
,这里列举一些简单命令,比较全的命令参考Redis sentinel API
# This command simply returns PONG.
PING
SENTINEL masters
# Show the state and info of the specified master.
SENTINEL master mymaster
# Show a list of replicas for this master, and their state.
SENTINEL slaves mymaster
# Show a list of sentinel instances for this master, and their state.
SENTINEL sentinels mymaster
# Return the ip and port number of the master with that name. If a failover is in progress or terminated successfully for this master it returns the address and port of the promoted replica.
SENTINEL get-master-addr-by-name mymaster
选举
只要有一个sentinel节点完成故障转移,就会涉及到选举.通过sentinel is-master-down-by-addr
命令都希望成为领导者,过程如下:
1. 每个做主观下线的`sentinel`节点向其他`sentinel`节点发送命令,要求将它设置为领导者
2. 收到命令的`sentinel`节点如果没有同意通过其他`sentinel`节点发送的命令,那么将同意该请求,否则拒绝.
3. 如果该`sentinel`节点发现自己的票数已经超过`sentinel`集合半数且超过`quorum`(`conf中对于监控配置中的法定人数的设置 sentinel monitor master_name ip port quorum`)
4. 如果此过程有多个Sentinel节点成为了领导者,那么将等待一段时间重新进行选举.
Java客户端
Jedis测试
JedisSentinelPool mymaster = new JedisSentinelPool("mymaster", Sets.newHashSet( "127.0.0.1:8379", "127.0.0.1:8380", "127.0.0.1:8381"));
Jedis resource = mymaster.getResource();
while (true) {
TimeUnit.SECONDS.sleep(1);
try {
System.out.println(resource.get("hh"));
} catch (Exception e) {
System.out.println("getting...");
}
}
Springboot测试
yml
这里其实配置host
没有实际作用,只不过利用了yml文件的特点,统一了取值.客户端会根据哨兵发送SENTINEL get-master-addr-by-name mymaster
命令获取当前的主节点信息.
spring:
#NoSQL缓存
redis:
database: 0
host: ${REDIS_HOST:192.168.0.12}
password: ${REDIS_PASSWORD:}
port: ${REDIS_PORT:6379}
timeout: 3000
sentinel:
master: mymaster
nodes: ${spring.redis.host}:8379,${spring.redis.host}:8380,${spring.redis.host}:8381
RedisConfig
这里忽略其他配置,如序列化等等,可以参考spring boot集成redis的基本配置,只展示相关的RedisConnectionFactory
类的配置,我使用LettuceConnectionFactory
包裹自定义的RedisSentinelConfiguration
来实现.
@Autowired private RedisProperties redisProperties;
@Bean
@Primary
public RedisSentinelConfiguration redisSentinelConfiguration() {
RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration();
sentinelConfiguration.setDatabase(redisProperties.getDatabase());
RedisProperties.Sentinel sentinel = redisProperties.getSentinel(); sentinelConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
sentinelConfiguration.setMaster(sentinel.getMaster());
List<String> nodes = sentinel.getNodes();
List<RedisNode> collect = nodes.stream()
.map(n -> {
String[] split = n.split(":");
return new RedisNode(split[0], Integer.parseInt(split[1]));
})
.collect(Collectors.toList());
sentinelConfiguration.setSentinels(collect);
return sentinelConfiguration;
}
@Bean
@Primary
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisSentinelConfiguration());
}
配置完成后, 简单的启动类测试代码:
@SpringBootApplication
public class IApplication implements ApplicationRunner {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public static void main(String[] args) {
SpringApplication.run(IApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
String hello = stringRedisTemplate.opsForValue().get("hello");
System.out.println(hello);
}
}
问题
最好不要在哨兵配置文件里面填写127.0.0.1
,如果不在服务器本机的话,客户端不认识.
参考