分布式Redis集群--哨兵架构

哨兵架构

sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。

在这里插入图片描述

工作原理

  • sentinel集群启动完毕后,会将哨兵集群的元数据信息写入到所有sentinel的配置文件中(追加在文件的
    最下面)
  • 当redis主节点如果宕机,哨兵集群会重新选举出新的redis主节点,同时会修改所有sentinel节点配置文件
    的集群元数据信息
  • 当宕机了的redis实例再次启动时,哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点
    加入集群
  • client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过
    sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis
    主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)

环境安装

前提:续上一篇【分布式Redis集群–主从架构】,已有Redis集群有一主两从,master(6379), slave(6380, 6381)

  1. 进入redis安装目录,拷贝一份sentinel.conf配置文件更名为sentinel-26379.conf

cd /usr/local/redis-5.0.3
cp sentinel.conf sentinel-26379.conf
vi sentinel-26379.conf

  1. 修改sentinel-26379.conf

#修改端口
port 26379
#设置后台运行
daemonize yes
#将PID进程号写入到pidfile文件
pidfile “/var/run/redis‐sentinel‐26379.pid”
logfile “26379.log”
#指定数据存放目录
dir “/usr/local/redis-5.0.3/data”
#sentinel monitor master‐redis‐name master‐redis‐ip master‐redis‐port quorum
#master‐redis‐name名字随便取,客户端访问时会用到
#master‐redis‐ip master‐redis‐port 监控的master节点的ip和port
#quorum取数值,表示有多少个sentinel认为一个master失效才算真正失效(基于半数原则,一般取:sentinel总数/2 + 1)
sentinel monitor mymaster 192.168.126.130 6379 2

  1. 保存配置并退出,启动sentinel-26379实例

:wq
src/redis-sentinel sentinel-26379.conf

  1. 重复步骤2,3启动sentinel-26380实例和sentinel-26381实例(注意:步骤2中修改相应sentinel端口号即可)

  2. 检查sentinel集群是否都启动成功

ps -ef|grep redis

在这里插入图片描述

  1. 查看任何一个sentinel实例的配置文件(文件最下面有追加集群元数据信息),看是否有更新集群元数据信息(以sentinel-26379.conf为例)

cat sentinel-26379.conf

sentinel实例配置文件中追加了集群元数据信息,包含已感知到的slave节点信息以及其他sentinel节点信息
在这里插入图片描述

  1. 客户端连接sentinel实例(以sentinel-26379为例)

src/redis-cli -p 26379
info

通过info命令可以看出,sentinel已经识别出Redis主从信息
在这里插入图片描述
在这里插入图片描述

  1. 演示Redis 主节点6379宕机,看sentinel是否会选出其他从节点作为新的主节点

#杀掉主节点6379的进程
kill -9 9442
ps -ef|grep redis

在这里插入图片描述

cat sentinel-26379.conf

可以看到,6379主节点挂掉后,sentinel选举出新的主节点6381,原来的6379已变成从节点
在这里插入图片描述
在这里插入图片描述

info

在这里插入图片描述

  1. 演示已宕机的6379节点重新启动,sentinel集群会根据集群元数据信息将6379节点作为从节点加入集群

src/redis-server redis.conf_master
ps -ef|grep redis

在这里插入图片描述

SpringBoot整合Redis哨兵

引入依赖

<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>

修改application.yml

server:
  port: 8080

#redis sentinel config
spring:
  application:
    name: redis-demo
  redis:
    database: 0
    timeout: 3000
    sentinel:
      master: mymaster
      nodes: 192.168.126.130:26379,192.168.126.130:26380,192.168.126.130:26381
    lettuce:
      pool:
        max-idle: 50
        min-idle: 10
        max-active: 100
        max-wait: 1000

RedisConfig

package com.itjeffrey.redis.test.service;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * Redis配置
 * @From: Jeffrey
 */
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    //string serializer
    private RedisSerializer<String> redisSerializer = new StringRedisSerializer();

    //object serializer
    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

    public RedisConfig(){
        setObjectMapper(jackson2JsonRedisSerializer);
    }

    //LettuceConnectionFactory实例化过程中会自动从spring.cache.redis中读取配置信息
    @Autowired
    private LettuceConnectionFactory lettuceConnectionFactory;

    /**
     * config redisTemplate---manually add caches
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置ObjectMapper(解决查询缓存转换异常问题)
        setObjectMapper(jackson2JsonRedisSerializer);
        // 设置连接工厂
        template.setConnectionFactory(lettuceConnectionFactory);
        // 配置key, value, hashValue序列化
        template.setKeySerializer(redisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    /**
     * 解决查询缓存转换异常问题
     */
    private void setObjectMapper(Jackson2JsonRedisSerializer jackson2JsonRedisSerializer){
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
    }
}

RedisUtil

package com.itjeffrey.redis.test.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis操作工具类
 * @From: Jeffrey
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 写入缓存
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存设置时效时间
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, timeUnit);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 批量删除对应的value
     *
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 批量删除key
     *
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<Serializable> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     *
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 哈希 添加
     *
     * @param key
     * @param hashKey
     * @param value
     */
    public void hmSet(String key, Object hashKey, Object value) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put(key, hashKey, value);
    }

    /**
     * 哈希获取数据
     *
     * @param key
     * @param hashKey
     * @return
     */
    public Object hmGet(String key, Object hashKey) {
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }

    /**
     * 列表添加
     *
     * @param k
     * @param v
     */
    public void lPush(String k, Object v) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(k, v);
    }

    /**
     * 列表获取
     *
     * @param k
     * @param l
     * @param l1
     * @return
     */
    public List<Object> lRange(String k, long l, long l1) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k, l, l1);
    }

    /**
     * 集合添加
     *
     * @param key
     * @param value
     */
    public void add(String key, Object value) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key, value);
    }

    /**
     * 集合获取
     *
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     *
     * @param key
     * @param value
     * @param scoure
     */
    public void zAdd(String key, Object value, double scoure) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key, value, scoure);
    }

    /**
     * 有序集合获取
     *
     * @param key
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rangeByScore(key, scoure, scoure1);
    }
}

测试

package com.itjeffrey.redis.test.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

/**
 * @From: Jeffrey
 */
@Service
public class TestService {

    private int count;

    @Autowired
    private RedisUtil redisUtil;

    @Scheduled(cron = "0 0/2 * * * ?")
    public void test(){
        String key = "charset" + count;
        redisUtil.set(key, "utf-" + count);
        System.out.println("set redis cache, key:" + key + " - value:" + redisUtil.get(key));
        count++;
    }
}

结果

控制台打印:
在这里插入图片描述
连接其中一个从节点6380并查看数据
在这里插入图片描述