redis主从结构与哨兵机制

主从架构

创建 一主两从文件夹。

mkdir master slave1 slave2

复制 etc/redis/redis.conf 文件分别到三个文件夹下。

cp /etc/redis/redis.conf /master
cp /etc/redis/redis.conf /slave1
cp /etc/redis/redis.conf /slave2

依次修改配置
设置master端口号为7000,slave1端口号为7001,slave2端口号为7002
依次修改master、slave1、slave2中bind为0.0.0.0
在从slave1,slave2配置文件中找到slaveof

在这里插入图片描述

取消注释后第一个数据填写master的bind 第二个数据填写master的端口号

salveof 0.0.0.0 7000

配置好之后使用下列代码依次打开7000、7001、7002端口

reids-server master/redis.conf
reids-server slave1/redis.conf
reids-server slave2/redis.conf

打开三个页面分别进入master、slave1、slave2

redis-cli =p 7000 --raw
redis-cli =p 7001 --raw
redis-cli =p 7002 --raw

在这里插入图片描述

此时:
master节点进行写操作成功后,会将数据同步到两个slave节点中。
端口7001、7002的slave变成了只读状态,无法进行写操作。
即使master宕机,从节点slave也无法暂时替代master结点进行写操作。

为了实现master宕机后,可以有新的节点来替代master进行写操作,就有了哨兵机制

哨兵机制

当主节点宕机后,会自动选出一个从节点来替代主节点进行读写操作。

创建sentinel.conf文件,加入一行

sentinel monitor 给该哨兵命名(自己起名) 主节点ip 主节点端口号 需要几票同意(单数)
例子: sentinel monitor mymaster 0.0.0.0 7000 1

解释一下最后一项 在sentinel中有一套自己的算法,当主节点宕机后sentinel会在从节点中选举出一个新的主节点。而最后的数字就代表需要几票才能通过选举,一定要写单数。

安装redis-sentinel

apt install redis-sentinel

安装后使用:

redis-sentinel /master/sentinel.conf

进入sentinel


根据上一节的主从设置打开三个结点master、slave1、slave2。此时运行

redis-cli -p 7000 shutdown

关闭当前主节点模拟宕机。


这时哨兵会等待主节点响应,默认为15秒,如需更改时间,在配置文件中加入

sentinel down-after-milliseconds mymaster 5000

代表主节点5秒没有相应就触发选举。



redis中测试

开启三个节点,开启哨兵

在这里插入图片描述
可以看到最下面,当前主节点为端口号7001对应的节点,现在我把7001端口关闭。

在这里插入图片描述
此时可以看到,经过redis的哨兵机制选举出了端口7002为当前主节点。

下面去springboot中测试一下

在sentinel配置文件中port下方添加一句话

bind 0.0.0.0

表示开启远程连接

在springboot的配置文件中随着哨兵的选举更改配置中的端口号

#单节点
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0

redis依赖:

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.properties

server.port=8080

#单节点
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#开启二级缓存
mybatis-plus.configuration.cache-enabled=true

#开启日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

Emp实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp implements Serializable {
    private int id;
    private String occ;
}

EmpMapper

@Mapper
//开启缓存
@CacheNamespace(implementation = RedisCache.class,eviction = RedisCache.class)
@CacheNamespaceRef(EmpMapper.class)
开启事务支持
//@Transactional(propagation = Propagation.SUPPORTS)
@EnableTransactionManagement
public interface EmpMapper extends BaseMapper<Emp> {
}

ApplicationUtil 获取spring工厂

@Component
public class ApplicationUtil implements ApplicationContextAware {

    //拿到spring工厂数据
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

    //获取工厂中的方法,并返回
    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }
}

RedisCache 实现Cache接口

public class RedisCache implements Cache {

    //放入缓存的namespace  com.tan.UserMapper.UserMapper"
    private final String id;
    private RedisTemplate redisTemplate;
    public RedisCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    //缓存放入值
    @Override
    public void putObject(Object o, Object o1) {
        //通过applicationContext工厂工具类,获取redisTemplate
        getRedisTemplate();
        redisTemplate.opsForHash().put(id.toString(), getKeyToMD5(o.toString()), o1);
    }

    //redis中获取数据
    @Override
    public Object getObject(Object o) {
        getRedisTemplate();
        return redisTemplate.opsForHash().get(id.toString(), getKeyToMD5(o.toString()));
    }

    //根据指定的o 删除缓存
    @Override
    public Object removeObject(Object o) {

        return null;
    }

    //删除所有缓存
    @Override
    public void clear() {
        getRedisTemplate();
        redisTemplate.delete(id);
    }

    @Override
    public int getSize() {
        getRedisTemplate();
        return  redisTemplate.opsForHash().size(id).intValue();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    //防止代码冗余
    private void getRedisTemplate(){
        if (null == redisTemplate){
            redisTemplate = (RedisTemplate) ApplicationUtil.getBean("redisTemplate");
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        }
    }

    //统一key格式
    private String getKeyToMD5(String key){
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }
}

测试类

@SpringBootTest
class JedisApplicationTests {

    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void test1(){
        empMapper.insert(new Emp(1,"财务部"));
    }
}

咱们先用redis单节点配置进行测试:

#单节点
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0

在这里插入图片描述
可以看到提示信息无法在从节点写入数据,这就证明当前配置无法在主节点宕机后,再使用redis进行写操作。
那么现在就需要根据哨兵的选举来动态的改变当前可以进行写操作的节点:

#根据哨兵选举获取节点
#master是自己起的哨兵名字
#nodes是当前主节点地址和哨兵port
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=127.0.0.1:26379

修改之后再次运行程序,报错:该数据已存在。
这是因为刚才我运行过一次insert操作,缓存中出错不影响mysql存储数据。删掉mysql中对应数据再次运行:
在这里插入图片描述
运行成功。

现在我让当前主节点7002宕机,然后等哨兵推选出新的主节之后,再测试一下。
在这里插入图片描述

在这里插入图片描述
可以看到当前主节点为7001 ,springboot还是从当前哨兵中获取到了可以执行写操作的主节点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值