Springboot(四十六)SpringBoot3整合redis并配置哨兵模式

439 阅读14分钟

前边我有尝试在Springboot2.6框架中集成redis哨兵集群。但是呢,Springboot3中部署redis的配置和Springboot2中的配置完全不同。

 

我这里再来记录一下Springboot3中配置redis的全部代码。

 

上次我的redis是在centos服务器上直接安装的,这次在Springboot3的配置中,我的redis使用docker来部署。

 

一:docker部署redis和redis哨兵

安装docker这部分我就不介绍了,具体请移步《docker(一)linux安装docker

 

安装redis就不需要使用dockerfile了,太麻烦了。这里直接使用docker命令来创建docker容器。

1:redis配置文件

(1)主机配置文件:

# 开启密码验证(可选)
requirepass xxxxx
# 允许Redis外部连接,需要注释掉绑定的IP
bind 0.0.0.0 -::1
# 关闭保护模式(可选)
protected-mode no
# 注释掉daemonize yes,或者配置成daemonize no。因为该配置和docker run中的-d参数冲突,会导致容器一直启动失败
daemonize no
# 开启Redis数据持久化(可选)
appendonly yes
# 开放端口
port 6379

我这里的配置比较简单,能用的上的就是这些。其余的配置如果需要请自行添加。

(2)从机配置文件

# 开启密码验证(可选)
requirepass xxxxx
# 允许Redis外部连接,需要注释掉绑定的IP
bind 0.0.0.0 -::1
# 关闭保护模式(可选)
protected-mode no
# 注释掉daemonize yes,或者配置成daemonize no。因为该配置和docker run中的-d参数冲突,会导致容器一直启动失败
daemonize no
# 开启Redis数据持久化(可选)
appendonly yes
# 开放端口
port 6380
# 配置主机
replicaof 你的ip 6379
# 配置主机密码
masterauth xxxxx

这里需要注意两个注意点:

(1):daemonize 需要配置为no。因为该配置和docker run中的-d参数冲突,会导致容器一直启动失败

(2):从节点的配置文件有点坑,最好是不要使用同一个端口,比如master使用了6379,slave容器就需要使用其他端口(6380)。尽管容器是互相隔离的,但网络通信这一块,会有问题。

我开始都使用了6379,映射宿主机的不同端口上,主从同步时是没有问题的,但是当主节点下线,或者重新加入节点时,哨兵节点会无法区分各个容器的。

虽然各个容器分配的ip不同,但sentinel通信时都使用了容器内部的回环地址172.17.0.1,这里可能是个bug。容器内直接使用不同端口,可以规避这个问题。我这里直接使用6379,6380,6381

 

2:创建docker容器命令:

1)      docker run -d -p 6379:6379 --name redis-master -v /opt/docker/redis/data/79:/data -v /opt/docker/redis/conf/redis79.conf:/etc/redis/redis.conf --privileged -d redis:7.4.0 redis-server /etc/redis/redis.conf
2)      docker run -d -p 6380:6380 --name redis-slave-one -v /opt/docker/redis/data/80:/data -v /opt/docker/redis/conf/redis80.conf:/etc/redis/redis.conf --privileged -d redis:7.4.0 redis-server /etc/redis/redis.conf
3)      docker run -d -p 6381:6381 --name redis-slave-two -v /opt/docker/redis/data/81:/data -v /opt/docker/redis/conf/redis81.conf:/etc/redis/redis.conf --privileged -d redis:7.4.0 redis-server /etc/redis/redis.conf

 

3:配置redis-sentinel容器

# 允许ip和端口
bind 0.0.0.0
port 26379
# 哨兵监听的ip和端口
sentinel announce-ip 127.0.0.1
# 后面的2  表示有2个哨兵认为主库挂了就是客观下线
# 主节点挂了 就会开始选举
sentinel monitor mymaster 127.0.0.1 6379 2
# 密码
sentinel auth-pass mymaster xxxxx
# 5000 表示每5秒钟检测一次主库是否挂掉
sentinel failover-timeout mymaster 5000
# linux中 解除redis保护 允许外部连接
protected-mode no
# 后台访问 这个参数 yes 会和docker run 命令冲突  导致redis 启动失败
daemonize no
# 主节点或副本在指定时间内没有回复PING,便认为该节点为主观下线 S_DOWN 状态。 默认是30秒
sentinel down-after-milliseconds mymaster 30000
# 安全
# 避免脚本重置,默认值yes
# 默认情况下,SENTINEL SET将无法在运行时更改notification-script和client-reconfig-script。
# 这避免了一个简单的安全问题,客户端可以将脚本设置为任何内容并触发故障转移以便执行程序。
sentinel deny-scripts-reconfig yes

 

4:创建docker-sentinel容器命令:

1)      docker run -d -p 26379:26379 --name redis-sentinel-one -v /opt/docker/redis/conf/sentinel26379.conf:/etc/redis/sentinel.conf -v /opt/docker/redis/sentinel/26379:/data -d redis:7.4.0 redis-sentinel /etc/redis/sentinel.conf
2)      docker run -d -p 26380:26380 --name redis-sentinel-two -v /opt/docker/redis/conf/sentinel26380.conf:/etc/redis/sentinel.conf -v /opt/docker/redis/sentinel/26380:/data -d redis:7.4.0 redis-sentinel /etc/redis/sentinel.conf
3)      docker run -d -p 26381:26381 --name redis-sentinel-three -v /opt/docker/redis/conf/sentinel26381.conf:/etc/redis/sentinel.conf -v /opt/docker/redis/sentinel/26381:/data -d redis:7.4.0 redis-sentinel /etc/redis/sentinel.conf

我这里创建三个哨兵,redis高可用,redis-sentinel也要高可用。

 

5:测试一下哨兵是否好用

配置了6379端口是主机。6379的配置文件中就不需要配置主机信息:

# 配置主机
replicaof 39.99.144.212 6379
# 配置主机密码
masterauth xxxxxx

 

但是,现在将6379关机,哨兵会自动选取新的主机,这个时候,再将6379开机,就需要手动修改一下6379的配置文件,主机信息加入到配置文件中:

# 配置主机(假设哨兵选举的新主机是6381)
replicaof 39.99.144.212 6381
# 配置主机密码
masterauth xxxxx

 

二:Springboot3集成redis-sentinel

1:添加pom依赖

<!-- springboot redis start       -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Spring集成Redis组件 -->
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-redis</artifactId>
</dependency>

<!-- redis链接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

 

2:application.yml配置

spring:
  data:
    redis:
      # 超时时间
      timeout: 10000
      # 使用的数据库索引,默认是0
      database: 0
      #host: 39.99.144.212
      #port: 6379
      # 密码
      password: 密码
      ###################以下为red1s哨兵增加的配置###########################
      sentinel:
        master: mymaster       # 哨兵主节点名称,自定义
        nodes: ip:port, ip:port, ip:port
        password: 密码
      ##################以下为]ettuce连接池增加的配置###########################
      lettuce:
        pool:
          max-active: 100  #连接池最大连接数(使用负值表示没有限制)
          max-idle: 100  #连接池中的最大空闲连接
          min-idle: 50 #连接池中的最小空闲连接
          max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)

 

3:redisconfig.java

package com.modules.redis.config;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
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.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
 
import java.util.HashSet;
 
@Configuration
@EnableCaching  //开启redis缓存
public class RedisConfig {
 
    @Autowired
    private RedisProperties redisProperties;
 
    /**
     * sentinel 集群配置(我在配置文件中配置,代码配置优先级要大于yml配置文件配置)
     * @return
     */
    /*@Bean
    public RedisConnectionFactory lettuceConnectionFactory()
    {
        RedisProperties.Sentinel sentinel = redisProperties.getSentinel();
        HashSet<String> nodes = new HashSet<>(sentinel.getNodes());
        String master = sentinel.getMaster();
        System.out.println("=====================================================================");
        System.out.println(sentinel.getNodes().toString());
        System.out.println(master);
        RedisSentinelConfiguration config = new RedisSentinelConfiguration(master, nodes);
        config.setDatabase(redisProperties.getDatabase());
        return new LettuceConnectionFactory(config);
    }//*/
 
    /*@Bean
    public RedisConnectionFactory lettuceConnectionFactory()
    {
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
                .master("mymaster")
                .sentinel("1.15.157.156", 26379)
                .sentinel("1.15.157.156", 26380)
                .sentinel("1.15.157.156", 26381);
        return new LettuceConnectionFactory(sentinelConfig);
    }//*/
 
    /**
     * redisTemplate 配置
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<StringObjectredisTemplate(RedisConnectionFactory factory)
    {
        RedisTemplate<StringObject> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        return redisTemplate;
    }
 
    /**
     * Redis 缓存配置
     * @param redisTemplate
     * @return
     */
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<StringObject> redisTemplate) {  // 明确指定参数化类型
        if (redisTemplate == null || redisTemplate.getConnectionFactory() == null) {
            // 处理错误情况,例如抛出异常或采取默认行为
            throw new RuntimeException("RedisTemplate or its connection factory is null");
        }
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
 
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }
 
    /**
     * 对hash类型的数据操作
     */
    @Bean
    public HashOperations<StringStringObjecthashOperations(RedisTemplate<StringObject> redisTemplate)
    {
        return redisTemplate.opsForHash();
    }
 
    /**
     * 对redis字符串类型数据操作
     */
    @Bean
    public ValueOperations<StringObjectvalueOperations(RedisTemplate<StringObject> redisTemplate) {
        return redisTemplate.opsForValue();
    }
 
    /**
     * 对链表类型的数据操作
     */
    @Bean
    public ListOperations<StringObjectlistOperations(RedisTemplate<StringObject> redisTemplate) {
        return redisTemplate.opsForList();
    }
 
    /**
     * 对无序集合类型的数据操作
     */
    @Bean
    public SetOperations<StringObjectsetOperations(RedisTemplate<StringObject> redisTemplate) {
        return redisTemplate.opsForSet();
    }
 
    /**
     * 对有序集合类型的数据操作
     */
    @Bean
    public ZSetOperations<StringObjectzSetOperations(RedisTemplate<StringObject> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
 
    /**
     * 对GEO类型的数据操作
     */
    @Bean
    public GeoOperations<StringObjectgeoOperations(RedisTemplate<StringObject> redisTemplate) {
        return redisTemplate.opsForGeo();
    }
 
    /**
     * 对hyperloglog类型的数据操作
     */
    @Bean
    public HyperLogLogOperations<StringObjecthyperLogLogOperations(RedisTemplate<StringObject> redisTemplate) {
        return redisTemplate.opsForHyperLogLog();
    }
}

 

到这里,你的redis其实就可以使用了。

 

4:redis工具类

package com.modules.redis.utils;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
 
import java.util.*;
import java.util.concurrent.TimeUnit;
 
@Service
public class RedisUtil {
 
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
 
    public RedisUtil()
    {
 
    }
    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;
        }
    }
 
}

 

5:redis+lua脚本分布式锁工具类

package com.modules.redis.utils;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
 
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
 
/**
 * @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 = 100;
 
    /**
     * 用来加锁的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 "
                    + " local number = redis.call("del", KEYS[1]) "
                    + " return tostring(number) "
                    + "else "
                    + " return tostring(0) "
                    + "end ";
 
    /**
     * 检查 redisKey 是否上锁(没加锁返回加锁)
     *
     * @param redisKey redisKey
     * @param template template
     * @return Boolean
     */
    public static Boolean isLock(String redisKey, String value, RedisTemplate<ObjectObject> template)
    {
 
        return lock(redisKey, value, template, RETRY_TIMES);
    }
 
    private static Boolean lock(String redisKey, String value, RedisTemplate<ObjectObject> 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;
    }
 
    /**
     * 加锁
     * @param key
     * @param value
     * @param template
     * @return
     */
    private static Boolean lockKey(final String key, final String value, RedisTemplate<ObjectObject> 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
            );
            //System.out.println("callback:"+callback);
            boolean result = template.execute(callback);
            //System.out.println("result:"+result);
            return result;//*/
            Boolean nativeLock = template.opsForValue().setIfAbsent(key,value, Duration.ofSeconds(RedisLuaUtils.TIMEOUT_MILLIS));
            System.out.println("加锁成功:"+nativeLock);
            return nativeLock;//*/
        }
        catch (Exception e)
        {
            log.info("lock key fail because of ", e);
            //throw new Exception("redis 连接失败!");
            return false;
        }
    }
 
    /**
     * 释放分布式锁资源(解锁)
     *
     * @param redisKey key
     * @param value value
     * @param template redis
     * @return Boolean
     */
    public static Integer releaseLock(String redisKey, String value, RedisTemplate<ObjectObject> 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);
            //*/
            // Integer result = template.execute(new DefaultRedisScript<>(UNLOCK_LUA,Integer.class), Collections.singletonList(redisKey),value);
            List<Object> list = new CopyOnWriteArrayList<>();
            list.add(redisKey);
            Integer result = template.execute(new DefaultRedisScript<>(UNLOCK_LUA,Integer.class), list, value);
            return result;//*/
        }
        catch (Exception e)
        {
            log.info("release lock fail because of ", e);
            return 0;
        }
    }
 
}
 
/**
 调用示例
 @Resource
 private RedisTemplate<ObjectObject> 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";
 }
 */

 

以上大概就是Springboot3集成redis哨兵模式的基本使用。

 

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