redis分布式锁
pom.xml
<!-- Spring Session redis start -->
<!-- Spring Session 依赖start -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<!-- Spring Session 依赖end -->
<!-- Spring session redis 依赖start -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
<!-- Spring session redis 依赖end -->
<!-- spring-data-redis依赖的JAR配置start -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.8.RELEASE</version>
</dependency>
<!-- spring-data-redis依赖的JAR配置end -->
<!-- jedis依赖的JAR配置start -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- jedis依赖的JAR配置end -->
<!-- Spring Session redis end -->
RedisConfig
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 43200) // 设置超时为 12 小时
public class RedisConfig {
static {
redisHost = ConfigCst.getConfigStringByKey("redis.host", "127.0.0.1");
redisPort = ConfigCst.getConfigIntByKey("redis.port", 6379);
redisPass = ConfigCst.getConfigStringByKey("redis.pass", "");
}
private static String redisHost;
private static int redisPort;
private static String redisPass;
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHost);
factory.setPort(redisPort);
factory.setPassword(redisPass);
factory.setUsePool(true);
return factory;
}
@Bean
RedisTemplate< String, Object > redisTemplate() {
final RedisTemplate< String, Object > template = new RedisTemplate< String, Object >();
template.setConnectionFactory( jedisConnectionFactory() );
template.setKeySerializer( new StringRedisSerializer() );
template.setHashValueSerializer( new GenericToStringSerializer< Object >( Object.class ) );
template.setValueSerializer( new GenericToStringSerializer< Object >( Object.class ) );
return template;
}
}
RedisLockUtils
@Component
public class RedisLockUtils {
private static final Logger logger = LoggerFactory.getLogger(RedisLockUtils.class);
@Resource
private RedisTemplate redisTemplate;
/**
* 尝试获取锁
*
* @param key 锁的键
* @param value 锁的值
* @param expireTime 锁的过期时间
* @param timeUnit 时间单位
* @return 是否获取到锁
*/
public Boolean tryLock(String key, String value, long expireTime, TimeUnit timeUnit) {
logger.info("tryLock redis -key:" + key);
logger.info("tryLock redis -value:" + value);
logger.info("tryLock redis -expireTime:" + expireTime);
logger.info("tryLock redis -timeUnit:" + timeUnit);
// 将过期时间转换为秒
long expireSeconds = timeUnit.toSeconds(expireTime);
return tryLockInternal(key, value, expireSeconds);
}
/**
* 尝试获取没有超时时间的锁
*
* @param key 锁的键
* @param value 锁的值
* @return 是否获取到锁
*/
public Boolean tryLockWithoutTimeout(String key, String value) {
logger.info("tryLockWithoutTimeout redis -key:" + key);
logger.info("tryLockWithoutTimeout redis -value:" + value);
// 传入过期时间为 -1,表示不设置过期时间
return tryLockInternal(key, value, -1);
}
/**
* 内部通用的尝试获取锁方法
*
* @param key 锁的键
* @param value 锁的值
* @param expireSeconds 锁的过期时间(秒),-1 表示不设置过期时间
* @return 是否获取到锁
* 脚本的作用是:
* 使用 SET 命令将 ARGV[1] 设置为 KEYS[1] 的值。
* 使用 NX 选项确保只有在 Key 不存在时才会设置成功。
* 使用 EX 选项设置 Key 的过期时间为 ARGV[2] 秒。
*/
private Boolean tryLockInternal(String key, String value, long expireSeconds) {
Boolean result = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
// 根据是否设置过期时间生成不同的 Lua 脚本
String script = "local result = redis.call('SET', KEYS[1], ARGV[1], 'NX'" +
// >0 设置超时(带有 EX 选项),否则不设置超时时间
(expireSeconds > 0 ? ", 'EX', ARGV[2]" : "") + ")" +
" if result then" +
" return 1" +
" else" +
" return 0" +
" end";
// 执行 Lua 脚本
return connection.eval(
script.getBytes(),
ReturnType.BOOLEAN,
1, // number of keys
// 由于 GenericToStringSerializer 最终会将字符串转换为字节数组,
// 因此可以直接传递字符串的字节数组,而不需要显式调用 serialize 方法
key.getBytes(),// KEYS[1]
value.getBytes(),// ARGV[1]
// >0 设置超时(带有 EX 选项),否则不设置超时时间
String.valueOf(expireSeconds).getBytes()// ARGV[2]
);
});
logger.info(key + " final result: " + (result != null && result));
return result != null && result;
}
/**
* 释放锁
*
* @param key 锁的键
*/
public void unlock(String key) {
redisTemplate.delete(key);
logger.info("redisTemplate-key:" + key + " remove success");
}
/**
* 释放锁
*
* @param key 锁的键
* @param value 锁的值
*/
public void unlock(String key, String value) {
// 在释放锁时验证 value
String currentValue = (String) redisTemplate.opsForValue().get(key);
if (value.equals(currentValue)) {
redisTemplate.delete(key);
logger.info("锁释放成功 - key: {}", key);
} else {
logger.info("锁释放失败 - key: {},value 不匹配", key);
}
}
/**
* 检查锁是否超时
*
* @param key 锁的 Key
* @return 是否超时(true:已超时;false:未超时)
*/
public boolean isLockExpired(String key) {
Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
if (ttl == null) {
// Key 不存在
logger.info("锁 {} 不存在,视为已超时", key);
return true;
}
if (ttl == -1) {
// Key 存在,但没有设置过期时间
logger.info("锁 {} 存在,但没有设置过期时间", key);
return false;
}
if (ttl <= 0) {
// Key 存在且已超时
logger.info("锁 {} 已超时,剩余时间: {} 秒", key, ttl);
return true;
}
// Key 存在且未超时
logger.info("锁 {} 未超时,剩余时间: {} 秒", key, ttl);
return false;
}
/**
* key 是否存在
*
* @param key
* @return
*/
public boolean isExistsHasKey(String key) {
return redisTemplate.hasKey(key);
}
}
Service
@Service
public class AServiceImpl extends ServiceImpl<AMapper, A> implements IAService {
@Resource
private RedisLockUtils redisLockUtils;
private static final long EXPIRE_TIME = 60; // 锁的过期时间,单位秒
private static final String LOCK_KEY = "to_lock";
@Override
public void toProduction() {
// 尝试获取锁
boolean isLocked = redisLockUtils.tryLock(LOCK_KEY, System.currentTimeMillis() + "", EXPIRE_TIME, TimeUnit.SECONDS);
if (!isLocked) {
throw new DataException("There is already a publish to production task in progress");
}
try {
// 具体业务流程
} finally {
// 释放锁
redisLockUtils.unlock(LOCK_KEY);
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!