说明:本案例模拟生产环境,使用六台装有redis的linux服务器(CentOS7),实现三主三从集群环境的搭建
redis高可用方案有四种,分别为:
(1)数据持久化RDB和AOF,redis4.0之后,引入RDB+AOF混合持久化
(2)主从复制,即数据读写分离
(3)哨兵模式(redis-sentinel),引入sentinel哨兵监控,实现自动故障恢复,弥补了主从复制下,主节点发生故障,需要人工介入手动恢复的不足
主从复制、哨兵模式虽然提高了读的并发,但是单个master容量是有限的,如果写操作的并发提高,那么redis就会达到性能的瓶颈,此时,就需要引入第四种高可用方案–集群部署
redis cluster是redis3.0之后推出的分布式集群解决方案,当遇到单机内存、并发、流量等瓶颈的时候,redis cluster可以起到很好的负载均衡的作用
redis cluster根据投票容错机制,需要半数以上主节点同意的原则,因此搭建redis集群至少需要6个节点(3主3从),其中主节点提供读写功能,从节点只作为备用节点,提供故障转移的作用,redis cluster采用虚拟槽分区,所有键根据哈希函数映射到0~16383个哈希槽内,每个节点负责一部分槽以及槽内映射的键值数据
redis cluster特点:
集群搭建前置条件:6台安装redis的linux服务器,也可选择一台服务器,搭建6个节点的伪集群环境
vim redis.conf
命令修改6台redis服务器的redis配置文件#开启集群模式
cluster-enabled yes
#每个节点需要一个配置文件,每个节点在集群的角色需要告知其他节点
cluster-config-file node-6379.conf
#设置超时时间,如果连接超过指定时间,则认为节点发生故障,自动切换至备用节点
cluster-node-timeout 5000
#开启AOF
appendonly yes
ruby工具
,结合redis安装包下提供的redis-trib.rb
脚本,实现集群搭建,redis5.0之后,redis-trib.rb
被集成到redis-cli中,本案例使用的redis版本是6.2.5,因此该案例直接通过redis-cli相关命令进行集群环境搭建(随机在一台服务器上执行即可)#执行以下命令,搭建集群环境
#其中执行redis-cli命令需要先进入redis安装bin目录下
#ip1:port1 ip2:port2 ip3:port3 ip4:port4 ip5:port5 ip6:port6 会对主从节点进行随机分配
#--cluster-replicas 1 声明一主对应一从的关系
redis-cli --cluster create ip1:port1 ip2:port2 ip3:port3 ip4:port4 ip5:port5 ip6:port6 --cluster-replicas 1
注:执行集群创建命令前,需要将6台redis服务器启动,否则该命令无法成功执行
3. 集群环境搭建完成后,执行redis-cli --cluster check ip:port
命令,可以查看各个节点的详细信息
从节点信息可以看到,其中192.168.132.138、192.168.132.139、192.168.132.140这三个节点被定义为主节点,分别分配了0-5460、5461-10922、10923-16383号哈希槽,从节点可以根据replicates
属性的编码值,查找到对应的主节点
4. 随机进入一台redis服务器的客户端,执行cluster info
命令查看集群信息
查看集群内节点情况,有两种方式进行查看:
①随机进入一台redis服务器的客户端,执行cluster nodes
命令查看
②通过生成的node-6379.conf文件进行查看,执行cat node-6379.conf
命令查看
测试其中一个主节点发生故障后的节点变化情况
从上图节点变化情况中可以看出,因redis.conf配置文件中设置的检查超时时间为15s,因此15s之前,192.168.132.140这台redis服务器连接正常,仍为master主节点,持续15s检测192.168.132.140仍未连接成功,则集群认为192.168.132.140发生故障,因此将其对应的141备用节点切换为主节点
数据存储情况查看
①进入192.168.132.138服务器redis客户端,执行set age 20
命令,存储一个键值对
②进入138主节点服务器对应的从节点142,执行keys *
命令查看是否进行主从复制操作
③随机选取其他节点查看,执行keys *
命令查看138主节点存储的数据是否可以查询到
注:从节点查看数据存储情况需要以集群方式进入:redis-cli -c
前置条件:搭建springboot工程,搭建redis cluster
<dependencies>
<!-- springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!-- springboot集成redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.3</version>
</dependency>
<!-- web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.3</version>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
</dependencies>
spring:
redis:
# redis集群节点声明
cluster:
nodes: 192.168.132.138:6379,192.168.132.139:6379,192.168.132.140:6379,192.168.132.141:6379,192.168.132.142:6379,192.168.132.143:6379
# 连接超时时间声明
connect-timeout: 5000
redisTemplate.opsForValue().get()
方法,经过验证,该方法只在redis单机环境下有效,redis集群环境下,无法进行读取@Data
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster") // 查找application.yml配置文件中声明的节点装载到对象属性
public class RedisProperties {
// 集群节点
private String nodes;
}
@Configuration
public class JedisClusterConfig {
@Resource
private RedisProperties redisProperties;
public JedisCluster getJedisCluster() {
// 读取redis cluster节点信息
String[] serverArray = redisProperties.getNodes().split(",");
Set<HostAndPort> nodes = new HashSet<>();
for (String server : serverArray) {
String[] ipPort = server.split(":");
nodes.add(new HostAndPort(ipPort[0].trim(), Integer.valueOf(ipPort[1].trim())));
}
return new JedisCluster(nodes, redisProperties.getCommandTimeout());
}
}
@Component
public class RedisClientTemplate {
@Resource
private JedisClusterConfig jedisClusterConfig;
public boolean setToRedis(String key, Object value) {
String s = jedisClusterConfig.getJedisCluster().set(key, String.valueOf(value));
if ("OK".equals(s)) {
return true;
}
return false;
}
public Object getRedis(String key) {
String s = jedisClusterConfig.getJedisCluster().get(key);
return s;
}
}
@Component
@RequestMapping("/redistest")
public class RedisClusterTest {
@Resource
public RedisClientTemplate redisClientTemplate;
@GetMapping("/getRedisValue/{key}")
@ResponseBody
public String getRedisValue(@PathVariable("key") String key) {
String str = (String) redisClientTemplate.getRedis(key);
return str;
}
}