理论系列-分布式锁-简单了解
文章目录
1. 是什么
锁,在多线程环境中控制对资源的并发访问,比如synchronized、Lock等
分布式锁,synchronized和Lock不只能本地加锁,或者说单机部署环境下的锁,在(单个服务)集群部署或者多服务完成一个接口时,需要用到分布式锁。
2. 应用场景
-
互联网秒杀
-
抢优惠券
-
…
3. 常见实现技术
MySQL
使用这么一个表:
列 | 作用 |
---|---|
id | |
resource_name | 锁定的资源名 |
node_info | 机器信息 |
count | 重入次数 |
desc | |
create_time | |
update_time |
上锁:resource_name,排它锁for update
解锁:resource_name查询到记录,然后机器信息验证通过,count–,等于0则删除
Zookeeper
Curator已经封装好了分布式锁的功能。
关键点:临时(有序)节点、watch(无需处理超时,机器宕机则自动删除节点)
Redis
setNx
setNx+Ex
Redisson已经封装好了分布式锁的功能。
关键点:原子命令
对比
略
- | MySQL | ZK | Redis |
---|---|---|---|
4. 关键
- 互斥性:只有一个线程获取锁
- 有效性:避免机器宕机(获取锁的时间,大于过期时间,需更新过期时间,防止业务未完成被释放,常见于Redis)
- 重入性
- 释放锁:上锁线程才有权释放锁
- 可选:阻塞、公平
5. 不安全
- GC的STW
- 时钟跳跃
- 长时间网络IO
6. Redis demo
场景:秒杀。提供一个接口,成功则库存-1,库存数保存在redis
1. 无锁
用户获取当前库存数,够则扣除1,否则结束
问题:多个用户访问?见2. 本地锁
@GetMapping("stock1")
@ResponseBody
public String stock() {
int curStock = Integer.parseInt(redisTemplate.opsForValue().get(KEY));
if (curStock > 0) {
int newStock = curStock - 1;
redisTemplate.opsForValue().set(KEY, String.valueOf(newStock));
System.out.println("扣除库存成功,剩余库存:" + newStock);
} else {
System.out.println("库存不足");
}
return "end";
}
2. 本地锁
多个用户访问,一个一个秒杀成功,直至没有库存数
问题:集群部署,用户A访问实例1,用户B访问实例1,如果剩余1个库存,可能AB都能秒杀成功
@GetMapping("stock2")
@ResponseBody
public String stock() {
synchronized (this) {
int curStock = Integer.parseInt(redisTemplate.opsForValue().get(KEY));
if (curStock > 0) {
int newStock = curStock - 1;
redisTemplate.opsForValue().set(KEY, String.valueOf(newStock));
System.out.println("扣除库存成功,剩余库存:" + newStock);
} else {
System.out.println("库存不足");
}
}
return "end";
}
3. Redis原生锁
前提:原子API,加锁并设置过期时间
第一点:用户获取锁,宕机。超时时间
第二点:业务时间 > 超时时间。定时器,定期更新
第三点:上锁和解锁为同一个线程
// 原子,同时完成加锁和过期时间设置
Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);
// 定时器,开个线程完成即可
// 上解锁,添加一个唯一标示,一般UUID即可
实现略。
4. Redisson分布式锁
@GetMapping("stock4")
@ResponseBody
public String stock4() {
RLock rLock = null;
try {
rLock = redisson.getLock(LOCK);
rLock.lock();
// 如果使用lock(int, int),不会自动更新超时时间
// try {
// Thread.sleep(35*1000);
// } catch (Exception e) {
// e.printStackTrace();
// }
int curStock = Integer.parseInt(redisTemplate.opsForValue().get(KEY));
if (curStock > 0) {
int newStock = curStock - 1;
redisTemplate.opsForValue().set(KEY, String.valueOf(newStock));
System.out.println("扣除库存成功,剩余库存:" + newStock);
} else {
System.out.println("库存不足");
}
} finally {
if (rLock != null) {
rLock.unlock();
}
}
return "end";
}
参考
https://juejin.im/post/5bbb0d8df265da0abd3533a5
https://www.bilibili.com/video/BV1d4411y79Y?from=search&seid=4094813974568007126