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哨兵模式到此完成。

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

相关推荐
重生之后端学习11 分钟前
02-前端Web开发(JS+Vue+Ajax)
java·开发语言·前端·javascript·vue.js
繁依Fanyi1 小时前
用 CodeBuddy 实现「IdeaSpark 每日灵感卡」:一场 UI 与灵感的极简之旅
开发语言·前端·游戏·ui·编辑器·codebuddy首席试玩官
来自星星的坤3 小时前
【Vue 3 + Vue Router 4】如何正确重置路由实例(resetRouter)——避免“VueRouter is not defined”错误
前端·javascript·vue.js
duapple5 小时前
Golang基于反射的ioctl实现
开发语言·后端·golang
字节源流6 小时前
关于maven的依赖下不下来的问题
java·maven
pjx9877 小时前
服务间的“握手”:OpenFeign声明式调用与客户端负载均衡
java·运维·spring·负载均衡
prinrf('千寻)7 小时前
MyBatis-Plus 的 updateById 方法不更新 null 值属性的问题
java·开发语言·mybatis
香蕉可乐荷包蛋7 小时前
浅入ES5、ES6(ES2015)、ES2023(ES14)版本对比,及使用建议---ES6就够用(个人觉得)
前端·javascript·es6
老华带你飞7 小时前
实习记录小程序|基于SSM+Vue的实习记录小程序设计与实现(源码+数据库+文档)
java·数据库·spring boot·小程序·论文·毕设·实习记录小程序
在未来等你8 小时前
互联网大厂Java求职面试:AI与大模型应用集成及云原生挑战
java·微服务·ai·kubernetes·大模型·embedding·spring ai