redis中的分布式锁(setIfAbsent)(expire)

本文介绍了在分布式系统中如何使用SpringDataRedis的setIfAbsent和expire方法来实现分布式锁,以解决数据一致性问题。通过实例展示了如何在任务调度、分布式事务、缓存同步和购买限制场景中应用这些技术,强调了权衡性能和复杂度的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

应用场景

代码实例1:

代码实例2:

setIfAbsent:

expire:

举例说明:

代码实例3:

代码实例4:


修改时间:2024年6月11日09:37:39

还是一个同事问的一个问题,然后闲着没事就记录下来了。多人操作同一个保单,会出现数据不一致,所以呢,就准备为此单子加一个锁,所以就有了下面的代码。分享给大家

分布式锁的定义大家基本都很了解了,具体就是当需要在分布式环境中保证资源的互斥访问或一致性时,就可以考虑使用分布式锁。但需要注意的是,分布式锁虽然可以解决问题,但也会带来一定的性能开销和复杂度,因此在使用时需要权衡系统的性能和可维护性,以及使用分布式锁所带来的成本和风险。

应用场景

  1. 发任务调度:在分布式系统中,当需要对某个任务进行调度时,如果多个节点同时发起任务调度请求,可能会导致任务被多次执行。这时可以使用分布式锁来确保只有一个节点能够成功获取到任务调度的锁,从而避免任务的重复执行。
  2. 分布式事务:在分布式事务中,需要保证不同节点对共享资源的访问是互斥的,以防止数据不一致的问题。分布式锁可以用来保证在事务执行过程中,各个节点之间对共享资源的访问是互斥的。
  3. 分布式缓存:在分布式缓存中,如果多个节点同时对缓存进行访问和修改,可能会导致缓存中的数据不一致。这时可以使用分布式锁来确保各个节点对缓存的访问是互斥的。
  4. 购买限制:在电商等场景中,如果需要对某个商品的购买数量进行限制,当多个用户同时发起购买请求时,可以使用分布式锁来确保只有一个用户能够成功获取到购买锁,从而避免超卖等问题。

代码实例1:

redisTemplate.opsForValue().setIfAbsent("ruleRefreshLock", uuid, 1000, TimeUnit.SECONDS);

这段代码是使用Spring Data Redis的RedisTemplate来执行一个Redis操作。具体来说,它尝试在Redis中设置一个键值对,但仅当该键不存在时。

详细解释如下:

  • "ruleRefreshLock":这是要设置的Redis键。
  • uuid:这是要设置的Redis值。
  • 1000:这是值的过期时间,单位是毫秒。
  • TimeUnit.SECONDS:这是过期时间的单位,这里是秒。

setIfAbsent方法的行为如下:

  • 如果键"ruleRefreshLock"在Redis中不存在,那么它会设置这个键的值为uuid,并给这个键设置一个1000秒的过期时间。此时,setIfAbsent方法返回true
  • 如果键"ruleRefreshLock"在Redis中已经存在,那么它不会做任何操作,并且setIfAbsent方法返回false

这个方法通常用于实现分布式锁。在这个例子中,你可能想要确保只有一个实例或线程能够获取到"ruleRefreshLock"这个锁。如果锁已经被其他实例或线程获取(即键已经存在),那么当前实例或线程就不会再尝试获取锁。如果锁未被获取(即键不存在),那么当前实例或线程就会获取锁,并设置一个过期时间以确保锁最终会被释放。

代码实例2:

设置KEY值过期时间

//假设 lockName 是你的锁的名称,expireTime 是你希望锁持续的时间(以分钟为单位)
redisTemplate.expire(lockName, expireTime, TimeUnit.MINUTES);

这段代码是使用 RedisTemplate 来设置 Redis 中某个键的过期时间。这与之前的 setIfAbsent 操作有所不同,主要区别如下:

setIfAbsent

  • 目的:尝试设置一个键值对,但仅当该键不存在时。
  • 返回值:如果键原本不存在且成功设置了键值对,返回 true;如果键已经存在,则不做任何操作并返回 false
  • 用途:通常用于实现分布式锁或其他需要原子性设置键值对的场景。

expire

  • 目的:为已存在的键设置或更新其过期时间。
  • 返回值:如果键存在,则成功设置或更新过期时间并返回 true;如果键不存在,则不进行任何操作并返回 false
  • 用途:通常用于确保存储在 Redis 中的数据在一段时间后被自动删除,以避免内存无限制增长。

举例说明:

假设你已经有了一个名为 lockName 的锁,并且这个锁已经被某个实例获取(即该键在 Redis 中已经存在)。你想让这个锁在一段时间后自动释放,那么你可以使用 expire 方法来设置锁的过期时间。

代码实例3:

redis版本        spring-data-redis-2.7.10.jar

@Slf4j
@Component
public class redisSchedule implements SchedulingConfigurer  {

    @Value("${rule.limit.cron}")
    private String cron;
    @Value("${schedule.rule}")
    private String scheduleRule;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addCronTask(() -> {
                // 分布式锁,集群环境只允许一台服务修改定时任务
                String uuid = UUID.randomUUID().toString();
                Boolean bool = redisTemplate.opsForValue().setIfAbsent("redisLock", uuid, 1000, TimeUnit.SECONDS);
                try {
                    if (bool) {
                        //逻辑代码
                    }
                } catch (Exception e) {
                    log.error(e.getMessage());
                } finally {
                    //  释放锁,先比对自己锁的值是否相等,相等则为自己的锁
                    String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                    redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("redisLock"), uuid);
                }
        }, cron);
    }
}

代码实例4:

redis版本        spring-data-redis-1.8.0.RELEASE.jar

//锁的过期时间,默认为5分钟
private Long expireTime = 5L;

try {
            //多个执行任务去获得锁,如果没有获取证明还有任务在执行中,那么将不再执行任务
            Boolean isLock = redisTemplate.opsForValue().setIfAbsent(lockName, "lock");
            if (isLock){
                //获取到锁,那么设置过期时间,防止死锁
                redisTemplate.expire(lockName, expireTime, TimeUnit.MINUTES);
                //逻辑代码
            }else {
                logger.info("任务执行锁定失败,Lock被占用,当前分片:=====");
                throw new Exception("任务执行锁定失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e.getMessage());
        }

总结一下,setIfAbsent 用于在键不存在时设置键值对,并返回操作是否成功的布尔值;而 expire 用于设置或更新已存在键的过期时间,并返回操作是否成功的布尔值。这两个操作经常一起使用来实现分布式锁,其中 setIfAbsent 用于尝试获取锁,而 expire 用于设置锁的过期时间以确保锁最终会被释放。两种方式的用法看对应的应用场景来使用

### Redis分布式锁续期机制的实现 在分布式系统中,使用Redis实现分布式锁时,为了避免死锁问题(如持有锁的线程崩溃导致锁无法释放),通常会为锁设置一个超时时间。然而,如果业务执行时间超过锁的超时时间,可能会导致锁被其他线程误抢。因此,引入分布式锁的续期机制是非常重要的。 #### 1. 分布式锁续期机制的核心思想 分布式锁的续期机制主要通过定期延长锁的有效期来实现,确保在业务逻辑未完成之前,锁不会因为超时而被释放。这种机制需要满足以下条件[^1]: - 在业务逻辑执行期间,持续检查锁是否仍然有效。 - 如果锁有效,则定期更新锁的过期时间。 - 如果锁无效或已被其他线程抢占,则停止续期并抛出异常。 #### 2. 基于RedisTemplate的续期实现 以下是基于`RedisTemplate`实现分布式锁续期机制的代码示例: ```java import org.springframework.data.redis.core.RedisTemplate; import java.util.concurrent.TimeUnit; public class DistributedLockWithRenewal { private final RedisTemplate<String, String> redisTemplate; private final String lockKey; private final String lockValue; private final long expireTimeMs; // 锁的初始超时时间(毫秒) private final long renewIntervalMs; // 续期间隔时间(毫秒) public DistributedLockWithRenewal(RedisTemplate<String, String> redisTemplate, String lockKey, String lockValue, long expireTimeMs, long renewIntervalMs) { this.redisTemplate = redisTemplate; this.lockKey = lockKey; this.lockValue = lockValue; this.expireTimeMs = expireTimeMs; this.renewIntervalMs = renewIntervalMs; } public boolean tryLock() { return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTimeMs, TimeUnit.MILLISECONDS); } public void unlock() { String currentValue = redisTemplate.opsForValue().get(lockKey); if (lockValue.equals(currentValue)) { redisTemplate.delete(lockKey); } } public void executeWithLock(Runnable task) throws InterruptedException { if (!tryLock()) { throw new IllegalStateException("Failed to acquire lock"); } try { Thread renewalThread = new Thread(() -> { while (redisTemplate.opsForValue().get(lockKey).equals(lockValue)) { try { Thread.sleep(renewIntervalMs); redisTemplate.expire(lockKey, expireTimeMs, TimeUnit.MILLISECONDS); // 续期 } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); renewalThread.start(); task.run(); // 执行业务逻辑 renewalThread.interrupt(); // 中断续期线程 renewalThread.join(); // 等待续期线程结束 } finally { unlock(); // 释放锁 } } } ``` #### 3. 使用Redisson实现续期机制 Redisson是一个功能强大的Redis客户端,内置了分布式锁的支持,并且自动实现了锁的续期机制。以下是基于Redisson的实现示例[^2]: ```java import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class RedissonDistributedLock { private final RedissonClient redissonClient; private final String lockKey; public RedissonDistributedLock(RedissonClient redissonClient, String lockKey) { this.redissonClient = redissonClient; this.lockKey = lockKey; } public void executeWithLock(Runnable task) { RLock lock = redissonClient.getLock(lockKey); try { lock.lock(30, TimeUnit.SECONDS); // 自动续期 task.run(); // 执行业务逻辑 } finally { lock.unlock(); // 释放锁 } } } ``` #### 4. 注意事项 - **锁的唯一性**:为了防止锁被误抢,通常会在锁值中加入唯一的标识(如UUID)[^1]。 - **续期频率**:续期频率应小于锁的超时时间,以确保在锁超时前完成续期。 - **线程安全**:续期操作应在单独的线程中进行,避免影响主业务逻辑的执行。 #### 5. 总结 分布式锁的续期机制是保证锁在长时间运行任务中有效性的重要手段。无论是基于`RedisTemplate`的手动实现,还是使用`Redisson`等高级客户端的自动实现,都需要合理设置锁的超时时间和续期频率,以避免死锁或误抢锁的问题。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一百减一是零

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值