redis 使用setIfAbsent方法加锁代码报错,为什么锁的key会进过期判断

文章讨论了在使用Redis的setIfAbsent方法实现加锁时遇到的错误,涉及到了键的过期判断机制。作者寻求帮助,关注于当数据失效时如何正确处理onMessage方法。

redis 使用setIfAbsent方法加锁代码报错,为什么锁的key会进过期判断

redis数据失效事件,进行数据处理的 onMessage方法

有没有大神可以帮忙解答一下

<think> 我们在讨论RedisTemplate实现分布式时,如果不使用Lua脚本,确实会出现问题。主要问题在于释放的操作无法保证原子性,可能会导致被误删。下面详细说明: ### 1. 为什么释放需要原子性? 当我们使用Redis实现分布式时,通常的步骤是: - 加锁使用 `SET key random_value NX PX 30000`(设置一个随机值,并设置过期时间) - 解:比较的值是否是自己设置的随机值,如果是则删除(即释放) 如果不使用Lua脚本,解操作需要两个步骤: 1. 获取的值(`GET key`) 2. 如果获取的值与当前线程持有的随机值匹配,则删除(`DEL key`) 这两个操作不是原子的。在并发场景下,可能出现以下问题: ### 2. 问题场景:被误删 假设线程A获取了过期时间为30秒,随机值为`uuidA`。当线程A执行完业务逻辑,准备释放时: 1. 线程A执行`GET lock_key`,得到值`uuidA`。 2. 此时,过期时间到了(比如因为业务执行时间较长),自动释放。 3. 线程B获得了,设置值为`uuidB`。 4. 线程A接着执行`DEL lock_key`,这时线程A删除的实际上是线程B的。 这样,线程B的被误删,导致其他线程(如线程C)可以获取,从而破坏了的互斥性。 ### 3. 如何解决? 使用Lua脚本,将获取的值和删除的操作合并成一个原子操作。脚本如下: ```lua if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end ``` 这个脚本在Redis中执行时是原子的,不会被打断。 ### 4. RedisTemplate中不使用Lua脚本的后果 在RedisTemplate中,如果不使用Lua脚本,而是分两步操作(先get再del),就会出现上述的误删问题。即使我们在代码中做了判断,例如: ```java String lockKey = "lock"; String lockValue = redisTemplate.opsForValue().get(lockKey); if (lockValue != null && lockValue.equals(uuid)) { redisTemplate.delete(lockKey); } ``` 这段代码在并发情况下依然不安全,因为get和delete是两个独立的操作,中间可能会有其他操作插入。 ### 5. 使用Lua脚本在RedisTemplate中的正确方式 在RedisTemplate中执行Lua脚本,需要以下步骤: 1. 定义Lua脚本字符串。 2. 使用`DefaultRedisScript`封装脚本,并指定返回值类型(如Long.class)。 3. 使用`redisTemplate.execute`方法执行脚本。 示例代码: ```java String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(script); redisScript.setResultType(Long.class); // 执行脚本 Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), uuid); if (result == 1) { System.out.println("释放成功"); } ``` ### 6. 注意事项 - **序列化问题**:RedisTemplate默认使用JDK序列化,可能导致存储的值与传入的字符串不一致。需要确保RedisTemplate的key和value的序列化器设置为`StringRedisSerializer`,否则Lua脚本中的字符串比较可能失败[^4]。 - **返回值类型**:`DefaultRedisScript`必须设置返回值类型,否则会报错[^1]。 ### 结论 **是的,RedisTemplate实现分布式如果不使用Lua脚本,在释放的时候会出现问题(误删),破坏的安全性。** 因此,必须使用Lua脚本来保证释放操作的原子性。 §§相关问题§§ 1. 如何在RedisTemplate中正确配置序列化器以避免分布式问题? 2. 除了Lua脚本,还有其他方法保证Redis操作的原子性吗? 3. 如何处理RedisTemplate执行Lua脚本时的序列化不一致问题? </think> ### RedisTemplate实现分布式使用Lua脚本的问题分析 是的,**不使用Lua脚本会导致严重的原子性问题**,可能破坏分布式的核心特性。以下是关键问题分析: #### 1. **释放的非原子性问题(核心风险)** 当释放时需执行两个操作: 1. 检查值是否匹配(GET操作) 2. 删除(DEL操作) 不使用Lua脚本的伪代码: ```java if(redis.get(lockKey).equals(myLockValue)) { // 步骤1 redis.delete(lockKey); // 步骤2 } ``` **风险场景**: 1. 线程A获取(值=uuidA) 2. A执行完业务,检查值匹配(通过) 3. 过期自动释放(恰在步骤1和2之间) 4. 线程B获取(值=uuidB) 5. A继续执行DEL操作,删除B的 → **互斥性被破坏** 引用明确指出:*"通过lua脚本来控制删除的原子性"*,避免这种并发问题[^2][^5]。 #### 2. **续期问题** 守护线程续期操作同样需要原子性: ```java // 非原子操作可能导致续期错误 if(redis.exists(lockKey)) { // 步骤1 redis.expire(lockKey, newTime); // 步骤2 } ``` **风险场景**: 1. 在步骤1后过期 2. 其他线程获取 3. 原线程执行步骤2 → **意外延长其他线程的** #### 3. **重入实现困难** 实现可重入需要原子地: 1. 检查持有者 2. 增加重入计数 不使用Lua脚本时,多个命令无法保证原子执行,可能导致计数错误[^5]。 #### 4. **序列化陷阱** RedisTemplate的序列化配置不当会导致值比较失效: ```java // 不同序列化器会导致值比较失败 Boolean success = redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue); // 值可能被序列化为二制 ``` 引用[^4]强调:*"FastJsonRedisSerializer和GenericFastJsonRedisSerializer的差异会导致比较失败"*[^4]。 --- ### 解决方案对比 | **实现方式** | **安全性** | **原子性** | **复杂度** | |-------------------|-----------|-----------|-----------| | 不使用Lua脚本 | ❌ 低风险 | ❌ 无保证 | ⭐ 简单 | | 使用Lua脚本 | ✅ 高安全 | ✅ 有保证 | ⭐⭐ 中等 | | Redisson框架 | ✅ 高安全 | ✅ 有保证 | ⭐⭐⭐ 复杂 | --- ### 结论 不使用Lua脚本的RedisTemplate分布式**存在严重缺陷**,主要问题包括: 1. 释放时可能误删其他线程的(破坏互斥性) 2. 续期操作可能延长错误 3. 重入实现不可靠 4. 序列化问题导致值比较失败 **强烈建议使用Lua脚本保证原子性**,正如引用[^1][^2][^3]强调的解决方案。在RedisTemplate中,应结合`DefaultRedisScript`和正确的序列化配置实现可靠分布式[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值