Redis - 高可用性:主从复制、哨兵模式及应用

高可用性

可用性(Availability):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)

99.9999%,一年仅停机31.5秒,根本感觉不到,而如果是99%,停机3.65天,如支付宝等应用会造成很大损失
在这里插入图片描述


单机服务

在简单系统使用Redis单机服务,实际情况会面临一些问题

  • 机器故障:单个Redis负责所有请求,压力较大,容错低,一旦出现故障整个系统缓存就瘫痪
  • 容量:Redis是内存服务器,受机器内存容量限制,单台redis服务器最大使用内存不应该超过20g

在这里插入图片描述

单机Redis可用性不高,且内存有限、性能有限


Redis主从模式

简介

既然一台Redis宕机会瘫痪系统,那么设置多个Redis服务器就好了

将一个Redis复制多个,原本的redis是master,复制的是slave
仅用master进行写操作,更新的数据同步到slave,slave进行读操作

这样做有两点好处:

  • 读写分离:提高系统负载能力,能够根据规模选择性增减slave
  • 数据备份:一台slave宕机不会影响系统,即高可用性

在这里插入图片描述

除了一主多从,还可以级联结构

在这里插入图片描述

注意:Redis主从模式只有一台master,可以有多台slave,当master宕机系统缓存失效

Redis主从复制配置

这里是windows下的redis,linux也一样的操作

下载解压Redis文件夹(Master - 6379端口)
在这里插入图片描述

复制两份 - 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 <masterip> <masterport>
slaveof 127.0.0.1 6379

masterauth 主服务器密码

一个端口为6381,,设置主Redis为本机6379端口

port 6381

# slaveof <masterip> <masterport>
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可以查看主从配置
在这里插入图片描述

springboot中操作主从复制

主从复制模式Redis启动了一个master和多个slave
类似于多数据源的Redis

如何操作多数据源Redis:Redis - 多数据库、多数据源及Springboot实现


哨兵模式

简介

主从复制虽然slave宕机不受影响,但是master宕机系统失效

解决方案:

  • Redis自身自动切换主从服务器
  • 用一个进程监视redis的运行状况,当master宕机时设置一台slave为master

哨兵模式(Redis-Sentinel):master宕机后,Redis本身没有实现自动进行主从切换,而是设置一个独立的进程Redis-Sentinel,部署在其他可以与Redis集群通讯的机器上,监视redis的运行状况:master宕机时设置一台slave为master

在这里插入图片描述

Redis哨兵模式配置

  1. sentinel.conf配置
    不同版本可能没有,没有的话就自己创建一个

只需要配置两句,其他的在后续运行过程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
  1. 按顺序启动:先启动master,再启动两个slave,最后启动哨兵

master:6379 ,从slave:6380、6381
在这里插入图片描述

# 启动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:

redis6380注释了slaveof
在这里插入图片描述

redis6381的slaveof的master改成了6380
在这里插入图片描述

多哨兵

前面的单哨兵监控1master和2slave,可能单哨兵会出问题,可以使用多哨兵监控
多个哨兵不仅监控redis,还会互相监控

在这里插入图片描述

前面的配置中:

  • sentinel monitor mySentinel 127.0.0.1 6379 1 1表示的是当有1个哨兵认为master宕机了就选举新的master,在多哨兵情况下需要按照系统要求设置
  • sdown和odown:sdown是主观宕机:一个哨兵自己觉得一个master宕机了
    odown是客观宕机:如果quorum数量(前面设置的1)的哨兵都觉得一个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

springboot中操作哨兵模式

项目:
在这里插入图片描述

  1. pom.xml
    常规的redis依赖:spring-boot-starter-data-redis、commons-pool2
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
  1. application.yml:redis、sentinel的相关配置

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

  1. 配置类RedisConfig:自定义序列化、配置RedisSentinelConfiguration
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;
    }
}

  1. 服务层RedisService
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);
    }
}
  1. 测试类
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");
        }
    }
}

测试结果:

  1. 因为不存在key,存入

在这里插入图片描述

  1. 第二遍取出key

在这里插入图片描述

  1. 现在挂掉master(6379)
    3个哨兵推荐6381
    为master

在这里插入图片描述

依旧正常运行
在这里插入图片描述


哨兵模式的缺陷

  • 单机模式:只有一个redis服务器,宕机后整个系统就失效了
  • 为了解决单机模式的问题,提出主从复制:复制redis,原redis叫master,负责写操作,复制的redis叫slave,负责读操作,slave宕机不会影响系统
  • 但是主从复制模式master宕机一样系统失效,提出哨兵模式:设置一个独立进程监视redis(master/slave),当master宕机时选择一个slave顶替master

哨兵模式中哨兵需要独立的进程启动,且需要不停的监视Redis服务器(心跳机制),耗费系统资源

解决方案: Redis Cluster


代码

代码:gitee有兴趣可以查看

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值