分佈式鎖簡單瞭解
文章目錄
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