Redis哨兵模式高可用模式搭建

300 阅读6分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

Redis高可用架构

image.png sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)

哨兵架构搭建

1、已经搭建好三个redis虚拟机(可以看我之前的文章),并设定master及slave 192.168.253.131:6379(master) 192.168.253.132:6380(slave) 192.168.253.133:6381(slave) 2、redis下有一个sentinel.conf文件,如下:

image.png

需修改以下内容:\

port 26379 
daemonize yes 
pidfile "/var/run/redis-sentinel.pid" 
logfile "sentinel.log" 
dir "/usr/local/redis/data" 
# sentinel monitor <master-redis-name> <master-redis-ip> <master-redis-port> <quorum> # quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效 
sentinel monitor mymaster 192.168.253.131 6379 2 
# mymaster这个名字随便取,客户端访问时会用到 

2、启动sentinel哨兵实例 
src/redis-sentinel sentinel.conf 

3、查看sentinel的info信息 
src/redis-cli -p 26379 127.0.0.1:26379>info 
可以看到Sentinel的info里已经识别出了redis的主从 

4、再配置另外两台sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改

查看131 master节点info(可以看到有两个slave节点,和节点信息)

image.png 查看132 slave节点info(当前节点role以及master节点信息)

image.png 查看133 slave节点info(当前节点role以及master节点信息)

image.png

集群信息查看

sentinel.conf从节点信息

sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面),我们查看下131 master配置文件sentinel.conf最下方,如下所示:

image.png

关闭131节点的redis服务。连接132及133节点redis客户端,查看info
132节点info信息如下:(已被哨兵选举为master节点,并可以看到slave就一个节点,节点信息为133)

image.png 再次打开132节点sentinel.conf文件,并到最下方,已经将挂掉的131,以及133作为从节点

image.png

sentinel.conf 主节点配置自动修改

哨兵在选举master的时候,同时还会修改sentinel.conf中主节点的信息
打开131 sentinel.conf配置文件如下:(可以看到之前131作为master的配置信息已更改为132)

image.png

打开132 sentinel.conf配置文件如下:(可以看到之前131作为master的配置信息已更改为132)

image.png

当131节点再次启动的时候就会被作为slave节点。

测试sentinel模式

public static void main(String[] args) throws IOException {

    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxTotal(20);
    config.setMaxIdle(10);
    config.setMinIdle(5);


    String masterName = "mymaster";
    Set<String> sentinels = new HashSet<String>();
    sentinels.add(new HostAndPort("192.168.253.131",26379).toString());
    sentinels.add(new HostAndPort("192.168.253.132",26380).toString());
    sentinels.add(new HostAndPort("192.168.253.133",26381).toString());
    //JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池
    //JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接
    JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);
    Jedis jedis = null;
    try {
        jedis = jedisSentinelPool.getResource();
        System.out.println(jedis.set("sentinel", "jony"));
        System.out.println(jedis.get("sentinel"));
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
        if (jedis != null)
            jedis.close();
    }
}

springboot 集成sentinel

1、配置pom.xml文件

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

2、springboot配置文件添加

#连接超时时间\
spring.redis.timeout: 3000\
#哨兵master(在sentinel.conf中配置)\
spring.redis.sentinel.master=mymaster\
#哨兵节点\
spring.redis.sentinel.nodes=192.168.253.131:26379,192.168.253.132:26380,192.168.253.133:26381\
#Redis数据库索引(默认为0)\
spring.redis.database=0\
#连接池配置,springboot2.0中直接使用jedis或者lettuce配置连接池,默认为lettuce连接池\
#连接池最大连接数(使用负值表示没有限制)\
spring.redis.jedis.pool.max-active=8\
#连接池最大阻塞等待时间(使用负值表示没有限制)\
spring.redis.jedis.pool.max-wait=-1s\
#连接池中的最大空闲连接\
spring.redis.jedis.pool.max-idle=8\
#接池中的最小空闲连接\
spring.redis.jedis.pool.min-idle=0

3、添加Controller

@RestController
public class IndexController {
    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试节点挂了哨兵重新选举新的master节点,客户端是否能动态感知到
     * 新的master选举出来后,哨兵会把消息发布出去,客户端实际上是实现了一个消息监听机制,
     * 当哨兵把新master的消息发布出去,客户端会立马感知到新master的信息,从而动态切换访问的masterip
     *
     * @throws InterruptedException
     */
    @RequestMapping("/test_sentinel")
    public void testSentinel() throws InterruptedException {
        int i = 1;
        while (true){
            try {
                stringRedisTemplate.opsForValue().set("jony"+i, i+"");
                System.out.println("设置key:"+ "jony" + i);
                i++;
                Thread.sleep(1000);
            }catch (Exception e){
                logger.error("错误:", e);
            }
        }
    }
}

浏览器访问测试,途中关闭master节点,测试redis可用性

测试结果

关闭master之后程序报错timeout,并且执行到了jony18 image.png 等待哨兵进行master选举,连接成功之后如下:(会显示上一次连接的maser节点,以及重新选举之后的master节点)

image.png 可以看到set的值,从jony19继续,无数据丢失

image.png

StringRedisTemplate与RedisTemplate详解

spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。
1、在RedisTemplate中提供了几个常用的接口方法的使用,分别是:

private ValueOperations<K, V> valueOps; 
private HashOperations<K, V> hashOps; 
private ListOperations<K, V> listOps; 
private SetOperations<K, V> setOps; 
private ZSetOperations<K, V> zSetOps;

2、RedisTemplate中定义了对5种数据结构操作

redisTemplate.opsForValue();//操作字符串 redisTemplate.opsForHash();//操作hash redisTemplate.opsForList();//操作list redisTemplate.opsForSet();//操作set redisTemplate.opsForZSet();//操作有序set

StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。

StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。

RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

Redis客户端命令对应的RedisTemplate中的方法列表:

String 结构

image.png

Hash结构

image.png

List结构

image.png

Set结构

image.png

Redis存储数据序列化:

StringRedisTemplate一般用来存储字符串,默认用的序列化是StringRedisSerializer。

如果要存储对象我们一般用RedisTemplate,它底层用的序列化机制是JdkSerializationRedisSerializer,这种存储对象要求对象实现Serializable接口,它底层存的是二进制的序列化数组,不便于在redis里查看,所以我们一般用Jackson2JsonRedisSerializer,能将对象转成json存储,并且不需要对象实现Serializable接口,也便于在redis里查看。

当然,如果不需要在redis里查看一些数据,对性能要求较高的话,序列化可以采用protobuf。

还有,对于key的话一般都使用StringRedisSerializer,参考示例:

RedisTemplate<Object, Object> template = new RedisTemplate<>();          
Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); 

// value的序列化采用jsonRedisSerializer  
template.setValueSerializer(jsonRedisSerializer); 
template.setHashValueSerializer(jsonRedisSerializer); 

// key的序列化采用StringRedisSerializer 
template.setKeySerializer(new StringRedisSerializer()); 
template.setHashKeySerializer(new StringRedisSerializer());