博主之前写了一篇Redis哨兵搭建,并没有对哨兵进行讲解,本篇填坑。
同时,也为博主写Redis分布式锁(二)做一些前置知识。
挖坑位置:Redis集群搭建(哨兵)
Redis主从
在讲redis哨兵前,需要先简单讲解一下redis主从。
俗话说,鸡蛋放在一个篮子里容易碎,那就把鸡蛋复制一份,放到其他篮子里。所有的高可用基本都是这个思路。
上一篇文章讲主从配置的时候,讲到一个配置属性slaveof
# 这个配置是redis-1中没有的,需要在redis-2中新增
# 这里的IP是redis-1的IP地址,端口是redis-1 6379.conf配置文件中port的值,默认值是6379
slaveof 1.1.1.1 6379
这个属性就是配置redis主从的。
这里分析以下上面这张图,可以发现以下几个特点
- 客户端可以从三个redis中读取数据
- 只有主库可以写入数据(抱歉,这个没有在图中体现)
- 两个从库从主库中读取数据
这种redis架构解决了以下问题
- 主库宕机,仍然可以在从库中读取数据,一定程度上提高了可用性
- 如果三个redis运行正常,数据应该一致
- 读取的压力分担到了3个节点上
存在以下几个问题
- 主库宕机,不能写入
- 主库不能自动切换,需要手动切换
- ※主库写入数据成功,还没来得及同步到从库,主库宕机
基于以上的一些问题,我们引出了redis哨兵
Redis哨兵
主从中存在一些问题是我们不能接受的,比如,主库宕机=无法写入。我们当然期望,宕机一个节点的时候,仍然可以对外服务,这才是高可用嘛~
哨兵
假如不能自动切换主库,我们该怎么做呢?运维童鞋,先不管主库了(已经宕机了),在从库中选择一个作为新的主库启动,优先提供服务嘛~至于主库,稍后再分析宕机原因,解决问题。
本着软件能解决的问题,就不使用人力,我们可不可以下一个软件,来代替运维童鞋的这些操作?
这个就是哨兵的功能雏形了。
哨兵监视主从redis,一旦redis主库宕机,哨兵切换主库,客户端再写入数据的时候,向新的主库中写入。在切换的时候,给运维童鞋一条通知,运维童鞋再处理。是不是很6?
优化
从上面可以看出,我们只有一个哨兵,如果这个哨兵宕机了,那我们的保障就没有了。
我们可以把哨兵也集群起来,优化结构如下:
分析
你可以把哨兵理解为zookeeper或者是eureka,哨兵相当于一个注册中心(当然不仅仅是注册中心的功能),客户端也从哨兵读取redis主从库信息。三个哨兵组成了一个集群的注册中心,当有一个哨兵宕机,还有其他哨兵存活,依然可以服务。
这样哨兵Sentinel也集群起来了,redis也有主从,我们这个时候可以说,提供了一套高可用的redis。
但这样的架构并不是绝对完美的,仍然存在一些问题。
依旧不能解决主从中的一个问题“主库写入数据成功,还没来得及同步到从库,主库宕机”。
例如主库上写入了一把锁,还没来得及把锁的信息同步到从库,主库挂了,从库切换为了主库,然而这个新的主库上并没有锁。引起了锁失效。
如何解决呢?
-
手动调整数据吧,少年。
-
如果是锁的场景,可以用zookeeper来代替redis分布式锁来解决。
哨兵客户端实现
博主使用的springboot来演示,使用工具包lettuce
依赖
<!-- data-redis中集成了lettuce -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis链接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- alibaba json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
配置文件
server:
port: 80
spring:
redis:
password: 密码
sentinel:
# 这个配置在 哨兵配置文件中
master: 你的集群名称
# 26379 端口是哨兵的默认端口
nodes: 10.101.36.19:26379,10.101.36.20:26379,10.101.36.21:26379
lettuce:
pool:
# 最大链接数
max-active: 30
# 链接池中最大空闲链接数
max-idle: 15
# 最大阻塞等待链接时长 默认不限制 -1
max-wait: 2000
# 最小空闲链接数
min-idle: 10
# 链接超时时长
shutdown-timeout: 10000
Redis 配置类
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis 配置类 将RedisTemplate交给spring托管
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
测试类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("test")
public class TestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String Test(){
redisTemplate.opsForValue().set("xujp", "sentinel");
String rst = (String) redisTemplate.opsForValue().get("xujp");
return rst;
}
}
测试
postman直接get请求即可
在redis连接工具中查看
主库:
从库:
这个时候把主库手动关闭
查看哨兵日志
可以看到主节点已切换完成
26721:X 20 Jul 2020 15:08:40.941 # +switch-master mymaster 10.101.36.20 6379 10.101.36.19 6379
这时候,在postman中再次发送请求
请求成功。
有兴趣的童鞋可以再测试一下内容:
- 在切换间隙尝试获取redis数据
- kill掉一个sentinel,再kill掉主库,查看主库切换
这里博主再挖一坑,redis哨兵模式切换主库的时候,如何通知运维人员呢?
总结
本文挖坑:
- redis哨兵模式切换主库的时候,如何通知运维人员呢?