SpringBoot(二十四)SpringBoot集成redis哨兵集群

前几天把redis又重新梳理了一次。主要还是对redis的查缺补漏以及更深入的了解。

这里算是一篇总结笔记叭,先从linux服务器redis主从复制,哨兵集群搭建开始。到后边springboot集成redis。

我们一步一步来。

一:redis搭建主从复制集群。

1:只需要配置从库,不需要配置主库

makefile 复制代码
127.0.0.1:6379> info replication         # 查看当前库信息
# Replication
role:master                                       # 角色
connected_slaves:0                            # 没有从机
master_failover_state:no-failover
master_replid:3cbed6e6f0629848071e0929f6d3f561d7498360
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:54
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

2:配置redis.conf

我没有多台服务器,因此我在一台服务器上边搭建redis集群,一主二从。三个redis。

首先我们来准备三个redis.conf配置文件:redis79.conf/redis80.conf/redis81.conf

我这里根据端口号命名。修改redis79.conf

bash 复制代码
port 6379        
       
daemonize yes
 
pidfile /var/run/redis_6379.pid
 
logfile "6379.log"
 
dbfilename dump6379.rdb
 
#如果你是用的是aof备份机制,还需要修改:
appendfilename "appendonly6379.aof"

3:配置从机

Redis默认是主机,因此,不需要配置主机,只需要配置从机即可。

配置从机很简单,让他知道那个是主机就可以了,我这里将79认定为主机,那么80/81两个就是从机。这个配置需要到80/81上边去操作:

配置主从,一般都在配置文件中配置,这样才是永久的。

修改从机中的redis.conf:

xml 复制代码
replicaof <masterip> <masterport>          # 配置主机
masterauth <master-password>              # 配置主机密码

依次修改上方的配置文件。

修改成功之后,依次使用配置文件启动redis-server

bash 复制代码
redis-server rconfig/redis79.conf
 
redis-server rconfig/redis80.conf
 
redis-server rconfig/redis81.conf

从机配置完成之后,我们再去主机下边看一下主机的信息:

makefile 复制代码
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2                                                                      # 两个从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=210,lag=0        # 从机1信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=210,lag=1        # 从机2信息
master_failover_state:no-failover
master_replid:29d7972ef844579fb6a727bbbce017e58e35c45e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:224
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:224

这里注意,每次修改完配置之后,重启redis需要将持久化文件(.rdb/.aof)文件删除,否则可能会有问题。

二:配置哨兵集群

创建哨兵模式配置文件sentinel.conf:

yaml 复制代码
# 绑定ip
bind 0.0.0.0
# 监听端口
port 26379
# 哨兵监听的ip和端口,这里监听ip要和springboot中配置的redis的ip一致
sentinel announce-ip "127.0.0.1"
# 后面的2  表示有2个哨兵认为主库挂了就是客观下线
# 就会开始选举
# 5000 表示每5秒钟检测一次主库是否挂掉,这里监听ip要和springboot中配置的redis的ip一致
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel failover-timeout mymaster 5000
# linux中 解除redis保护 允许外部连接
protected-mode no
# 后台访问
daemonize yes

更多sentienl配置请移步《Redis重制(十九)哨兵模式

这里需要注意一下:

yaml 复制代码
sentinel monitor mymaster 127.0.0.1 6379 1

中的mymaster,这个是redis主节点的名称,叫什么都行,这个要在后边的springboot配置中使用。

哨兵要保证高可用,也要配置集群,需要多个配置文件,监听不同端口,启动多个哨兵。

bash 复制代码
redis-sentinel rconfig/sentinel79.conf
redis-sentinel rconfig/sentinel80.conf
redis-sentinel rconfig/sentinel81.conf

这里需要注意,先启动redis集群,再启动哨兵集群。

三:springboot集成redis

1:配置application.yml配置文件

yaml 复制代码
spring:
  #redis
  redis:
    # 超时时间
    timeout: 10000
    # 使用的数据库索引,默认是0
    database: 0
    # 密码
    # password: 123456
    ###################以下为肌ed1s哨兵增加的配置###########################
    sentinel:
      master: mymaster       # 哨兵主节点名称,自定义这就是上边配置主节点的时候的名字
      nodes: 1.1.1.1:26379, 1.1.1.1:26380, 1.1.1.1:26381
    ##################以下为]ettuce连接池增加的配置###########################
    lettuce:
      pool:
        max-active: 100  #连接池最大连接数(使用负值表示没有限制)
        max-idle: 100  #连接池中的最大空闲连接
        min-idle: 50 #连接池中的最小空闲连接
        max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)

注意上边配置中的spring.redis.sentinel.master:mymaster

这其中的mymaster就是哨兵模式中,主节点的名称。

2:添加RedisConfig.java配置

typescript 复制代码
package com.modules.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.redis.support.collections.RedisProperties;


@Configuration
@EnableCaching //开启注解
@ConditionalOnClass() // 替换springboot原有配置
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * retemplate相关配置(这是一个固定模板,工作中可以直接用)
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(stringRedisSerializer);
        // 值采用json序列化
        template.setValueSerializer(jacksonSeial);

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();

        return template;
    }

    /**
     * redis哨兵模式配置类修改
* 这部分配不配应该都可以
     */
    /*@Bean
    public RedisSentinelConfiguration  redisSentinelConfiguration()
    {
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration()
                .master("mymaster")
                .sentinel("1.15.157.156",26379)
                .sentinel("1.15.157.156",26380)
                .sentinel("1.15.157.156",26381);
        return redisSentinelConfiguration;
    }//*/

    /**
     * 对hash类型的数据操作
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }

    /**
     * 对GEO类型的数据操作
     */
    @Bean
    public GeoOperations<String, Object> geoOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForGeo();
    }

    /**
     * 对hyperloglog类型的数据操作
     */
    @Bean
    public HyperLogLogOperations<String, Object> hyperLogLogOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHyperLogLog();
    }

    /**
     * 对bitmap类型的数据操作
     */
//    @Bean
//    public BitMapOperations<String, Object> opsForBitmap(RedisTemplate<String, Object> redisTemplate) {
//        return redisTemplate.opsForBitmap();
//    }

}

重要部分都有注释,对照即可。这个配置类是开箱即用的。

3:配置utils工具类

普通redis操作类:RedisUtil.java

kotlin 复制代码
package com.modules.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public RedisUtil(RedisTemplate<String, Object> redisTemplate)
    {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key,long time)
    {
        try
        {
            if(time>0)
            {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key)
    {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String ... key)
    {
        if(key!=null&&key.length>0)
        {
            if(key.length==1)
            {
                redisTemplate.delete(key[0]);
            }
            else
            {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key){
        return key==null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @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) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key,Object value,long time){
        try {
            if(time>0){
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key,String item){
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 使用模糊查询找到匹配的键
     * @param pattern
     * @return
     */
    public List<String> findKeysByPattern(String pattern) {
        // 使用模糊查询找到匹配的键
        Set<String> keys = redisTemplate.keys(pattern);
        List<String> list = new ArrayList<>(keys);
        // 返回所有匹配的键
        return list;
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String,Object> map, long time){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value,long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item,double by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item,double by)
    {
        return redisTemplate.opsForHash().increment(key, item,-by);
    }

    //============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key)
    {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key,Object value){
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object...values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key,long time,Object...values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if(time>0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object ...values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end){
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key,long index){
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index,Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key,long count,Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

}

redis加锁工具类:RedisLuaUtils.java

typescript 复制代码
package com.modules.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

import java.nio.charset.StandardCharsets;

/**
 * @author camellia
 * redis 加锁工具类
 */
@Slf4j
public class RedisLuaUtils
{
    /**
     * 超时时间(毫秒)
     */
    private static final long TIMEOUT_MILLIS = 15000;

    /**
     * 重试次数
     */
    private static final int RETRY_TIMES = 10;

    /***
     * 睡眠时间(重试间隔)
     */
    private static final long SLEEP_MILLIS = 500;

    /**
     * 用来加锁的lua脚本
     * 因为新版的redis加锁操作已经为原子性操作
     * 所以放弃使用lua脚本
     */
    private static final String LOCK_LUA =
            "if redis.call("setnx",KEYS[1],ARGV[1]) == 1 " +
                    "then " +
                    " return redis.call('expire',KEYS[1],ARGV[2]) " +
                    "else " +
                    " return 0 " +
                    "end";

    /**
     * 用来释放分布式锁的lua脚本
     * 如果redis.get(KEYS[1]) == ARGV[1],则redis delete KEYS[1]
     * 否则返回0
     * KEYS[1] , ARGV[1] 是参数,我们只调用的时候 传递这两个参数就可以了
     * KEYS[1] 主要用來传递在redis 中用作key值的参数
     * ARGV[1] 主要用来传递在redis中用做 value值的参数
     */
    private static final String UNLOCK_LUA =
            "if redis.call("get",KEYS[1]) == ARGV[1] "
                    + "then "
                    + " return redis.call("del",KEYS[1]) "
                    + "else "
                    + " return 0 "
                    + "end ";

    /**
     * 检查 redisKey 是否上锁(没加锁返回加锁)
     *
     * @param redisKey redisKey
     * @param template template
     * @return Boolean
     */
    public static Boolean isLock(String redisKey, String value, RedisTemplate<Object, Object> template)
    {

        return lock(redisKey, value, template, RETRY_TIMES);
    }

    private static Boolean lock(String redisKey, String value, RedisTemplate<Object, Object> template, int retryTimes)
    {
        boolean result = lockKey(redisKey, value, template);

        while (!(result) && retryTimes-- > 0)
        {
            try
            {
                log.debug("lock failed, retrying...{}", retryTimes);
                Thread.sleep(RedisLuaUtils.SLEEP_MILLIS);
            }
            catch (InterruptedException e)
            {
                return false;
            }
            result = lockKey(redisKey, value, template);
        }
        return result;
    }


    private static Boolean lockKey(final String key, final String value, RedisTemplate<Object, Object> template)
    {
        try
        {
            RedisCallback<Boolean> callback = (connection) -> connection.set(
                    key.getBytes(StandardCharsets.UTF_8),
                    value.getBytes(StandardCharsets.UTF_8),
                    Expiration.milliseconds(RedisLuaUtils.TIMEOUT_MILLIS),
                    RedisStringCommands.SetOption.SET_IF_ABSENT
            );

            return template.execute(callback);
        }
        catch (Exception e)
        {
            log.info("lock key fail because of ", e);
        }
        return false;
    }


    /**
     * 释放分布式锁资源
     *
     * @param redisKey key
     * @param value value
     * @param template redis
     * @return Boolean
     */
    public static Boolean releaseLock(String redisKey, String value, RedisTemplate<Object, Object> template)
    {
        try
        {
            RedisCallback<Boolean> callback = (connection) -> connection.eval(
                    UNLOCK_LUA.getBytes(),
                    ReturnType.BOOLEAN,
                    1,
                    redisKey.getBytes(StandardCharsets.UTF_8),
                    value.getBytes(StandardCharsets.UTF_8)
            );

            return template.execute(callback);
        }
        catch (Exception e)
        {

            log.info("release lock fail because of ", e);
        }
        return false;
    }

}

/**
 @Resource
 private RedisTemplate<Object, Object> redisTemplate;

 @PostMapping("/order")
 public String createOrder() throws InterruptedException {

 log.info("开始创建订单");

 Boolean isLock = RedisDistributedLock.isLock("testLock", "456789", redisTemplate);

 if (!isLock) {

 log.info("锁已经被占用");
 return "fail";
 } else {
 //.....处理逻辑
 }

 Thread.sleep(10000);
 //一定要记得释放锁,否则会出现问题
 RedisDistributedLock.releaseLock("testLock", "456789", redisTemplate);

 return "success";
 }
 */

//参考文章:
//https://blog.csdn.net/Z99263/article/details/140448173
//https://www.jb51.net/article/279367.htm
//https://blog.51cto.com/u_14844/8822845

代码配置完成。

下面我们来启动项目,启动项目没有问题,在我刷新页面的时候,报错:

vbnet 复制代码
org.springframework.data.redis.RedisSystemException: Error in execution; 
nested exception is io.lettuce.core.RedisReadOnlyException: READONLY You can't write against a read only replica.

报这个错,就说明我们的spring.redis.sentinel.master:mymaster这个配置项有问题。

1:首先检查代码中的配置项是否都是mymaster

2:其次再检查redis中的哨兵主节点名称是否是:mymaster

使用

bash 复制代码
redis-cli -p 26379  # 链接哨兵

执行:

bash 复制代码
info sentinel   # 查看sentinel信息
makefile 复制代码
[root@VM-4-16-centos bin]# redis-cli -p 26379
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=0,sentinels=3

如果redis中和代码中的名称都是mymaster。重启项目应该就没有问题了。

四:测试一下:

1:编写一个方法,测试一下:

csharp 复制代码
@Autowired
private RedisUtil redisUtil;
@GetMapping("index/redis")
public void testRedis() throws InterruptedException
{
    System.out.println("开始存储redis");
    redisUtil.set("kk1","123");
    // 线程睡一会儿
    Thread.sleep(100);
    // 获取redis存储值
    String kk1 = (String)redisUtil.get("kk1");
    // 打印
    System.out.println("kk1:"+kk1);
    System.out.println("redis获取结束");
}

控制台输出:

makefile 复制代码
开始存储redis
kk1:123
redis获取结束

2:进入服务器的redis-cli,查看kk1的值

csharp 复制代码
[root@VM-4-16-centos bin]# redis-cli -p 6379
127.0.0.1:6379> get kk1
""123""
127.0.0.1:6379>
[root@VM-4-16-centos bin]# redis-cli -p 6380
127.0.0.1:6380> get kk1
""123""
127.0.0.1:6380>

至此,redis搭建哨兵模式集群,以及springboot集成redis哨兵模式到此完成。

有好的建议,请在下方输入你的评论。

相关推荐
Asthenia0412几秒前
为什么MySQL关联查询要“小表驱动大表”?深入解析与模拟面试复盘
后端
南雨北斗3 分钟前
分布式系统中如何保证数据一致性
后端
J总裁的小芒果5 分钟前
el-table 自定义列、自定义数据
前端·javascript·vue.js
晚风予星6 分钟前
简记|React+Antd中实现 tooltip、ellipsis、copyable功能组件
前端·react.js
Asthenia04127 分钟前
Feign结构与请求链路详解及面试重点解析
后端
WuWuII7 分钟前
gateway
java·gateway
左灯右行的爱情10 分钟前
缓存并发更新的挑战
jvm·数据库·redis·后端·缓存
浩宇软件开发14 分钟前
Android开发,实现一个简约又好看的登录页
android·java·android studio·android开发
brzhang14 分钟前
告别『上线裸奔』!一文带你配齐生产级 Web 应用的 10 大核心组件
前端·后端·架构
程序员Bears14 分钟前
深入理解CSS3:Flex/Grid布局、动画与媒体查询实战指南
前端·css3·媒体·visual studio code