简单了解什么是分布式锁
比如:
白话文就是、如果使用本地锁(Synchronized)的话、在微服务中、单个服务是可以锁的、假如开多个实例、通过网关进行访问、多个实例A、B、C、D 服务都会从新查询、锁不住多个实例、
分布式锁就是解决这一问题、多个实例共享同一个中间件里面的值(redis)
分布式锁会出现集中问题、带着问题码着代码肯定是思路清晰到你懂的
分布式锁产生的(5中)问题
1)、阶段一
setnx占好了位、业务代码异常或者程序在页面过程中宕机、没有执行删除逻辑、这就造成了死锁
解决:
设置锁的自动过期、即使没有删除、也会自动过期
2)、进阶到阶段二
获取到锁之后、就设置过期时间
1、sentnx设置好、正要去设置过期时间、宕机又死锁了
解决:
设置过期时间和占位必须是原子的、redis支持使用sentnx ex命令
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(“lock”, “111”, 300, TimeUnit.SECONDS);
3)、进阶到阶段三
1、删除锁直接删除??
如果由于业务时间很长、锁自己过期了,我们直接删除、有可能把别人正在持有的锁删除了
解决:
站锁的时候、值指定为uuid,每个人匹配自己的锁才删除(保证删锁的时候不是别人的锁)
String uuid = UUID.randomUUID().toString();
//设置过期时间、必须和加索是同步的、原子的、要不全成功、要不全失败
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
String lockValue = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lockValue)){
stringRedisTemplate.delete("lock");
}
4)、进阶到阶段四
如果正好判断是当前值、正要删除锁的时候,锁已经过期了,别人已经设置到了新值、那吗我们删除的是别人的锁
解决:
删除锁必须保证原子性。使用redis+Lua脚本完成、带删除线 ~~ 就是分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
/**
* 1、将数据库的多次查询变为一次
* 改造业务
*
* 占分布式锁、去redis占坑
*/
~~String uuid = UUID.randomUUID().toString();
//设置过期时间、必须和加索是同步的、原子的、要不全成功、要不全失败
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);~~
if (lock) {
System.out.println("获取分布式锁成功");
Map<String, List<Catelog2Vo>> dataFromDb;
try {
//加锁成功 ---执行业务 ----也得先看缓存中有没有
dataFromDb = getDataFromDb();
} finally {
/**
* redis 如果获取 指定的lock(KEYS[1]) key == 指定的 uuid (ARGV[1]) 值
* 然后就调用 redis (then return redis.call('del',KEYS[1])) 删除方法 把这个key(KEYS[1])) 删除
* 否则就返回0 (else return 0 end")
* 而且删除成功返回的是 1
*/
~~String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";~~
//原子删锁
~~Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);~~
}
//设置过期时间、必须和加索是同步的、原子的、要不全成功、要不全失败
//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS); //设置过期时间
//执行删除redis 问题:不是原子性----使用Lua脚本
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if(uuid.equals(lockValue)){
// stringRedisTemplate.delete("lock");
// }
return dataFromDb;
} else {
System.out.println("获取分布式锁不成功");
//加锁失败。。。重试--还是的重新站锁-直接重新在调用一下自己的方法
return getCatalogJsonFromDbWithRedisLock();//自旋的方式、相当于synchrozied他会监听到你释放锁之后立马在占锁
}
}
5)、阶段五-最终形态
保证加锁 【占位+过期时间】和删除锁 【判断+删除】 的原子性。更难的事情,锁的自动续期
redis分布式锁两大核心
①、加锁保证原子性
②、解锁保证原子性
这样才能锁住所有的服务
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
其实redis布式锁就这两步、可以根据自己的业务进行改造
主要方面:
1、原子性加锁
2、原子性解锁
解释下面的原子性解锁的脚本
redis 如果获取 指定的lock(KEYS[1]) key == 指定的 uuid (ARGV[1]) 值
然后就调用 redis (then return redis.call('del',KEYS[1])) 删除方法 把这个key(KEYS[1])) 删除
否则就返回0 (else return 0 end")
而且删除成功返回的是 1
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
/**
* 占分布式锁、去redis占坑
*/
String uuid = UUID.randomUUID().toString();
//设置过期时间、必须和加索是同步的、原子的、要不全成功、要不全失败
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
System.out.println("获取分布式锁成功");
Map<String, List<Catelog2Vo>> dataFromDb;
try {
//加锁成功 ---执行业务 ----也得先看缓存中有没有
dataFromDb = getDataFromDb();
} finally {
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//原子删锁
Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDb;
} else {
System.out.println("获取分布式锁不成功");
//加锁失败。。。重试--还是的重新站锁-直接重新在调用一下自己的方法
return getCatalogJsonFromDbWithRedisLock();//自旋的方式、相当于synchrozied他会监听到你释放锁之后立马在占锁
}
}
展示效果图
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/15538.html