前边我有尝试在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<String, Object> redisTemplate(RedisConnectionFactory factory)
{
RedisTemplate<String, Object> 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<String, Object> 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<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();
}
}
到这里,你的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<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;
}
/**
* 加锁
* @param key
* @param value
* @param template
* @return
*/
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
);
//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<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);
//*/
// 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<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";
}
*/
以上大概就是Springboot3集成redis哨兵模式的基本使用。
有好的建议,请在下方输入你的评论。