Redis哨兵

Posted 江流大海_浮萍如尘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis哨兵相关的知识,希望对你有一定的参考价值。

博主之前写了一篇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哨兵模式切换主库的时候,如何通知运维人员呢?

以上是关于Redis哨兵的主要内容,如果未能解决你的问题,请参考以下文章

Redis主从与哨兵架构详解 Redis主从架构 如何在同一台机器搭建主从架构 Redis主从工作原理 数据部分复制 Jedis使用 Redis的管道(Pipeline) Redis Lua脚本(代码

高级开发运维从简单学:Redis哨兵和集群小贴士

高级开发运维从简单学:Redis哨兵和集群小贴士

redis主从+哨兵

Redis的哨兵(Sentinel)分析

Spring之redis主从复制(非哨兵模式)