还是一个同事问的一个问题,然后闲着没事就记录下来了。多人操作同一个保单,会出现数据不一致,所以呢,就准备为此单子加一个锁,所以就有了下面的代码。分享给大家
分布式锁的定义大家基本都很了解了,具体就是当需要在分布式环境中保证资源的互斥访问或一致性时,就可以考虑使用分布式锁。但需要注意的是,分布式锁虽然可以解决问题,但也会带来一定的性能开销和复杂度,因此在使用时需要权衡系统的性能和可维护性,以及使用分布式锁所带来的成本和风险。
应用场景
- 发任务调度:在分布式系统中,当需要对某个任务进行调度时,如果多个节点同时发起任务调度请求,可能会导致任务被多次执行。这时可以使用分布式锁来确保只有一个节点能够成功获取到任务调度的锁,从而避免任务的重复执行。
- 分布式事务:在分布式事务中,需要保证不同节点对共享资源的访问是互斥的,以防止数据不一致的问题。分布式锁可以用来保证在事务执行过程中,各个节点之间对共享资源的访问是互斥的。
- 分布式缓存:在分布式缓存中,如果多个节点同时对缓存进行访问和修改,可能会导致缓存中的数据不一致。这时可以使用分布式锁来确保各个节点对缓存的访问是互斥的。
- 购买限制:在电商等场景中,如果需要对某个商品的购买数量进行限制,当多个用户同时发起购买请求时,可以使用分布式锁来确保只有一个用户能够成功获取到购买锁,从而避免超卖等问题。
代码实例1:
redisTemplate.opsForValue().setIfAbsent("redisLock", 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){ //获取到锁,那么设置过期时间,防止死锁 this.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
用于设置锁的过期时间以确保锁最终会被释放。两种方式的用法看对应的应用场景来使用