赞
踩
分布式单锁解决的是多个线程操作同一个资源时冲突的问题。
然而,在有些场景下,例如一个用户的购物车中多件商品的库存更改(多个资源),这些资源作为一个整体,需要保证同时被锁住,全部完成修改才可释放锁,此时就需要使用分布式联锁。
使用redis原生命令加锁:
SET key value EX seconds NX
其中:
key
锁名,通常命名为:业务模块:资源id,例如 book:stock:1 表示操作的资源为 id为1的图书对象的库存,这样命名能保证线程之间冲突(冲突才能被锁住)
value
加锁者,通常为 ip:线程号 用于标注加锁的线程
EX seconds
加锁时长(秒) ,可选,设置加锁时长,到时无论线程是否完成资源操作,均会释放锁,防止死锁的发生。
NX
该参数能实现效果:只有当不存在该key的锁时才会新增key,否则无法新增。如果没有NX参数,已存在同名key时,SET命令做的是修改操作,该参数也是实现锁的关键。
解锁:
使用redis内置的lua脚本,先判断加锁者是否是当前解锁者,若是则删除key,若否则抛异常回滚。
因为redis执行lua脚本是阻塞的,这样可保证操作的原子性。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
<dependencies>
...
<!--版本全局指定-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
...
</dependencies>
spring:
redis:
host: xxx.xxx.xxx.xxx # ip
port: 6379 # 默认端口
password: ****** # 密码 没有则不写
@Autowired private StringRedisTemplate template;// 引入redis private static final String LOCK = "sum:mod:1";// 锁名 private static final Integer SECS = 5;// 过期时间 秒 防止死锁 public void run(){ ValueOperations<String, String> operations = template.opsForValue();// 获取操作redis的接口对象operations String name = Thread.currentThread().getName();// 线程名 // 上锁,不存在则新增并返回true 否则返回false 参数:锁名 线程名 有效期 时间粒度 Boolean success = operations.setIfAbsent(LOCK, name, SECS, TimeUnit.SECONDS); // 判断是否拿到锁 拿到则执行 否则不执行 if (Boolean.TRUE.equals(success)) { // 主业务代码... if(operations.get(LOCK)==null) throw new RuntimeException(name+"锁已超时释放(rollback)"); else if (!name.equals(operations.get(LOCK))) throw new RuntimeException("加锁解锁线程不匹配"); template.delete(LOCK);// 执行完毕 释放锁 } else { System.out.println(name+"没有拿到锁,继续等待"); } }
<dependencies>
...
<!--用redisson替代spring-boot-redis-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>{指定版本 例如3.19.1}</version>
</dependency>
....
</dependencies>
@Resource
private RedissonClient redisson;
// 执行添加操作
public void add(Integer id, Integer num){
...
RLock lock = redisson.getLock("sum:mod:" + id);// 生成锁 参数为锁名
try {
lock.lock(5, TimeUnit.SECONDS);// 加锁 设置5秒过期
// 主业务代码...
} finally {
lock.unlock();// 解锁
}
...
}
@Resource private RedissonClient redisson; private static final String LOCK="book:stock:";// 锁名 根据业务需要取名 // 传入的参数为要同时锁住的资源个数 public void demo(Integer lockNum) { ... // 生成分布式锁 RLock[] locks = new RLock[lockNum]; for (int i=0; i< lockNum; i++) locks[i] = redisson.getLock(LOCK + i); RLock multiLock = redisson.getMultiLock(locks);// 生成联锁 try { multiLock.lock(5, TimeUnit.SECONDS);// 加锁 设置5秒过期 // 主业务代码,操作多个资源,例如使用for循环... }finally { multiLock.unlock();// 解锁 } ... }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。