1、单体单机部署的系统被演化成分布式集群系统后
2、由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效
3、单纯的Java API 并不能提供分布式锁的能力
4、为了解决这个问题就需要一种跨JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题
5、示意图(说明: 我们探讨的分布式锁是针对分布式项目/架构而言[.])
解读:
解读
解读
解读
解读
设置锁的同时, 指定该锁的过期时间,防止死锁
这个指令是原子性的,防止setnx key value / expire key seconds 两条指令, 中间执行被打断.
过期时间到后, 会自动删除
–如果获取到该分布式锁
–就获取key 为num 的值, 并对num+1, 再更新num 的值, 并释放锁(key 为lock)
–如果获取不到key 为num 的值, 就直接返回
–如果没有获取到该分布式锁
–休眠100 毫秒, 再尝试获取
\src\main\java\com\redis\controller\RedisTestController.java , 增加API 接口
@GetMapping("testLock")
public void testLock() {
//1 获取锁,setnx
Boolean lock =
redisTemplate.opsForValue().setIfAbsent("lock", "ok");
//2 获取锁成功、查询num 的值
if (lock) {
Object value = redisTemplate.opsForValue().get("num");
//2.1 判断num 为空return
if (value == null || !StringUtils.hasText(value.toString())) {
return;
}
//2.2 有值就转成成int
int num = Integer.parseInt(value.toString());
//2.3 把redis 的num 加1
redisTemplate.opsForValue().set("num", ++num);
//2.4 释放锁,del
redisTemplate.delete("lock");
} else {
//3 获取锁失败、每隔0.1 秒再获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock
假如在执行关闭锁之前就发生异常然后没有执行释放锁 然后我们又没有设置过期时间 所以就会死锁
修改这一句就好了
注意这在实际开发中锁过期的时间是需要经过严格的考虑的不然设小了没有起效果 设置大了效率低
Boolean lock =
redisTemplate.opsForValue().setIfAbsent("lock", "ok", 3, TimeUnit.SECONDS);
ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock
注意因为我们前面测试过一次了所以再测试就是2000次了
@GetMapping("testLock")
public void testLock() {
//1 获取锁,setnx
//得到一个uuid 值,作为锁的值
String uuid = UUID.randomUUID().toString();
Boolean lock =
redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
//2 获取锁成功、查询num 的值
if (lock) {
Object value = redisTemplate.opsForValue().get("num");
//2.1 判断num 为空return
if (StringUtils.isEmpty(value)) {
return;
}
//2.2 有值就转成成int
int num = Integer.parseInt(value + "");
//2.3 把redis 的num 加1
redisTemplate.opsForValue().set("num", ++num);
//2.4 释放锁,del
//为了防止误删锁, 进行判断
//判断当前这个锁是不是前面获取到的锁, 相同才进行删除/释放
if (uuid.equals((String) redisTemplate.opsForValue().get("lock"))) {
redisTemplate.delete("lock");
}
//redisTemplate.delete("lock");
} else {
//3 获取锁失败、每隔0.1 秒再获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock
当前代码问题分析, 如图
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
//装配RedisTemplate
@Resource
private RedisTemplate redisTemplate;
//编写方法,使用Redis分布式锁,完成对 key为num的+1操作
@GetMapping("/lock")
public void lock() {
//得到一个uuid值,作为锁的值
String uuid = UUID.randomUUID().toString();
//1. 获取锁/设置锁 key->lock : setnx
Boolean lock =
redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
if (lock) {//true, 说明获取锁/设置锁成功
//这个key为num的数据,事先要在Redis初始化
Object value = redisTemplate.opsForValue().get("num");
//1.判断返回的value是否有值
if (value == null || !StringUtils.hasText(value.toString())) {
return;
}
//2.有值,就将其转成int
int num = Integer.parseInt(value.toString());
//3.将num+1,再重新设置回去
redisTemplate.opsForValue().set("num", ++num);
//释放锁-lock
//为了防止误删除其它用户的锁,先判断当前的锁是不是前面获取到的锁,如果相同,再释放
//=====使用lua脚本, 控制删除原子性========
// 定义lua 脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 使用redis执行lua执行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
// 设置一下返回值类型 为Long
// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
// 那么返回字符串与0 会有发生错误。
redisScript.setResultType(Long.class);
// 第一个是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值
// 解读 Arrays.asList("lock") 会传递给 script 的 KEYS[1] , uuid 会传递给ARGV[1] , 其它的应该很容易理解
redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
//if (uuid.equals((String) redisTemplate.opsForValue().get("lock"))) {
// //...
// redisTemplate.delete("lock");
//}
//redisTemplate.delete("lock");
} else { //获取锁失败,休眠100毫秒,再重新获取锁/设置锁
try {
Thread.sleep(100);
lock();//重新执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ab -n 1000 -c 100 http://192.168.198.1:8080/redisTest/testLock
1、定义锁的key, key 可以根据业务, 分别设置,比如操作某商品, key 应该是为每个sku 定义的,也就是每个sku 有一把锁
2、为了确保分布式锁可用,要确保锁的实现同时满足以下四个条件: