可用性(Availability):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)
99.9999%,一年仅停机31.5秒,根本感觉不到,而如果是99%,停机3.65天,如支付宝等应用会造成很大损失
在简单系统使用Redis单机服务,实际情况会面临一些问题
单机Redis可用性不高,且内存有限、性能有限
既然一台Redis宕机会瘫痪系统,那么设置多个Redis服务器就好了
将一个Redis复制多个,原本的redis是master,复制的是slave
仅用master进行写操作,更新的数据同步到slave,slave进行读操作
这样做有两点好处:
除了一主多从,还可以级联结构
注意:Redis主从模式只有一台master,可以有多台slave,当master宕机系统缓存失效
这里是windows下的redis,linux也一样的操作
复制两份 - slave :6380、6381端口
修改redis.windows.conf配置文件(Linux是redis.conf)
找到4个属性:port、slaveof、masterauth、slave-read-only
masterauth设置主服务器密码
slave-read-only yes 表示slave仅读,默认为yes,
一个端口6380,设置主Redis为本机6379端口
port 6380
# slaveof
slaveof 127.0.0.1 6379
masterauth 主服务器密码
一个端口为6381,,设置主Redis为本机6379端口
port 6381
# slaveof
slaveof 127.0.0.1 6379
masterauth 主服务器密码
master和两个slave都写一个启动脚本startRedisServer.bat(直接点redis-server.exe是不能点开的,要带着配置文件启动)
windows命令脚本,Linux是shell脚本
@echo off
redis-server.exe redis.windows.conf
@pause
启动:先启动master,再启动slave
master:redis-server
slave1:6380端口
slave2:6381端口
在master输入命令info replication
可以查看主从配置
主从复制模式Redis启动了一个master和多个slave
类似于多数据源的Redis
如何操作多数据源Redis:Redis - 多数据库、多数据源及Springboot实现
主从复制虽然slave宕机不受影响,但是master宕机系统失效
解决方案:
哨兵模式(Redis-Sentinel):master宕机后,Redis本身没有实现自动进行主从切换,而是设置一个独立的进程Redis-Sentinel,部署在其他可以与Redis集群通讯的机器上,监视redis的运行状况:master宕机时设置一台slave为master
只需要配置两句,其他的在后续运行过程redis会自动添加
# sentinel monitor + 自定义哨兵名 + masterIP + master端口
# 1代表1个哨兵认为主服务器不可用的时候,做故障切换操作(后续多哨兵模式需要配置)
sentinel monitor mySentinel 127.0.0.1 6379 1
# 设置master和slave的验证密码
# sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同
sentinel auth-pass mySentinel zfk857213
# 启动redis-sentinel
redis-server sentinel.conf --sentinel
+slave表示成功
sdown是主观宕机:一个哨兵自己觉得一个master宕机了
odown是客观宕机:如果quorum数量的哨兵都觉得一个master宕机了
现在关闭master,也就是主服务器宕机
slave显示:连接失败
哨兵监视:master6379宕机,sentinel选择slave6380作为master
哨兵改变master和slave,配置文件也会发生改变
sentinel.conf中sentinel monitor和known-slave都改变了
对应的redis6380和redis6381:
redis6381的slaveof的master改成了6380
前面的单哨兵监控1master和2slave,可能单哨兵会出问题,可以使用多哨兵监控
多个哨兵不仅监控redis,还会互相监控
前面的配置中:
sentinel monitor mySentinel 127.0.0.1 6379 1
1表示的是当有1个哨兵认为master宕机了就选举新的master,在多哨兵情况下需要按照系统要求设置sentinel1.conf:
sentinel monitor mymaster 127.0.0.1 6379 2
# Generated by CONFIG REWRITE
port 26379
# 设置连接master,slave密码
sentinel auth-pass mymaster zfk857213
sentinel1、sentinel3修改端口号
设置多份sentinel.conf,然后启动就多哨兵模式
哨兵报错:Unable to AUTH to MASTER: -ERR Client sent AUTH, but no password is set
这是密码问题,启动master的时候直接点了redis-server.exe,这样启动是不带redis.windows.conf的,我们配置的属性都是默认值也就是无密码启动
而后续的slave设置了masterauth主服务器密码
建议写一个启动脚本:startRedisServer.bat
@echo off
redis-server.exe redis.windows.conf
@pause
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
yml中sentinel只有两项配置:master前面设置的sentinel名、nodes对应的ip+port;连接密码需要在配置类中设置
通过sentinel就可以连接master、slave,并不需要设置redis的ip/port
spring:
application:
name: redis-sentinel-demo
redis:
timeout: 5000
sentinel:
master: mymaster
nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381
lettuce:
pool:
max-active: 8
max-wait: -1s
max-idle: 8
min-idle: 0
password: zfk857213
package cn.springboot.redis.sentinel.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Configuration
@EnableCaching
@Log
public class RedisConfig {
@Value("#{'${spring.redis.sentinel.nodes}'.split(',')}")
private List<String> nodes;
@Bean
public RedisSentinelConfiguration sentinelConfiguration(){
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
//配置matser的名称
redisSentinelConfiguration.master("mymaster");
//配置redis的哨兵sentinel
Set<RedisNode> redisNodeSet = new HashSet<>();
nodes.forEach(x->{
redisNodeSet.add(new RedisNode(x.split(":")[0],Integer.parseInt(x.split(":")[1])));
});
log.info("redisNodeSet -->"+redisNodeSet);
redisSentinelConfiguration.setSentinels(redisNodeSet);
//设置sentinel与master/slave的连接密码
redisSentinelConfiguration.setPassword("zfk857213");
return redisSentinelConfiguration;
}
@Bean
public LettuceConnectionFactory lettuceConnectionFactory(RedisSentinelConfiguration sentinelConfiguration){
LettuceConnectionFactory factory = new LettuceConnectionFactory(sentinelConfiguration);
return factory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerialize 替换默认的jdkSerializeable序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
package cn.springboot.redis.sentinel.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
log.error("exception when check key {}. ", key, e);
return false;
}
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
log.error("exception when set key {}. ", key, e);
return false;
}
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
}
package cn.springboot.redis.sentinel.test;
import cn.springboot.redis.sentinel.service.RedisService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
@Autowired
private RedisService redisService;
@Test
public void testCache() {
if (redisService.hasKey("sentinel")){
System.out.println(redisService.get("sentinel"));
}
else {
redisService.set("sentinel","Springboot - Redis");
}
}
}
测试结果:
哨兵模式中哨兵需要独立的进程启动,且需要不停的监视Redis服务器(心跳机制),耗费系统资源
解决方案: Redis Cluster
代码:gitee有兴趣可以查看