
什么是分布式鎖
為了防止分布式系統(tǒng)中的多個進程之間相互干擾,我們需要一種分布式協(xié)調(diào)技術(shù)來對這些進程進行調(diào)度。而這個分布式協(xié)調(diào)技術(shù)的核心就是來實現(xiàn)這個分布式鎖。
分布式鎖應該具備哪些條件
- 在分布式系統(tǒng)環(huán)境下,一個方法在同一時間只能被一個機器的一個線程執(zhí)行
- 高可用的獲取鎖與釋放鎖
- 高性能的獲取鎖與釋放鎖
- 具備可重入特性(可理解為重新進入,由多于一個任務并發(fā)使用,而不必擔心數(shù)據(jù)錯誤)
- 具備鎖失效機制,防止死鎖
- 具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗
試驗高并發(fā)場景
@RestController
@RequestMapping("/order")
@AllArgsConstructor
public class OrderController {
private static ReentrantLock lock = new ReentrantLock();
private StringRedisTemplate redisTemplate;
@GetMapping("/reduce_order")
public String executor() {
reduceOrder();
return "end";
}
public void reduceOrder() {
String productKey = "order";
try {
lock.lock();
int order = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(productKey)));
if (order > 0) {
int realorder = order - 1;
redisTemplate.opsForValue().set(productKey, String.valueOf(realorder));
System.out.println("訂單數(shù)-1,剩余訂單數(shù)量:" + realorder);
} else {
System.out.println("庫存數(shù)不足,請及時補充訂單");
}
} catch (Exception e) {
System.out.println("訂單扣除失敗");
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
上述代碼只是簡單一個模擬扣除訂單的方法,如果多個進程同時運行時候就會出現(xiàn)訂單重復扣減的問題,redis中設(shè)置了訂單數(shù)為50
使用jmeter測試
1.我們使用nginx
做轉(zhuǎn)發(fā)項目8081和8082端口
-
啟動項目8081和8082端口
image - 使用nginx 做轉(zhuǎn)發(fā)8081和8082端口
redis配置
user nginx;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream myapp1 {
# 換成主機ip
server 10.8.162.22:8081 weight=10;
server 10.8.162.22:8082 weight=10;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
index index.jsp index.html index.htm;
}
}
}
2.下載jmeter
3.啟動并配置jmeter
啟動jmeter
添加線程組
添加Http請求用例
配置jmeter
4.運行jmeter,查看結(jié)果
執(zhí)行
8081端口
8082端口
從結(jié)果中很明顯看出進程1-8081 和進程2-8082明顯重復扣除訂單
分布式鎖就是防止這種情況發(fā)生
redis分布式鎖的原理
當一個線程執(zhí)行 setnx 返回 1,說明 key 原本不存在,該線程成功得到了鎖;當一個線程執(zhí)行 setnx 返回 0,說明 key 已經(jīng)存在,該線程搶鎖失敗。
1.redis核心要素
- 加鎖 setnx
(1 成功獲取鎖 0 已有鎖 加鎖失?。?/code>
- 解鎖
直接刪除即可
- 鎖超時
(expire設(shè)置超時時間)
2.分布鎖問題
- 如何保證原子性
只設(shè)置key值還沒在設(shè)置超時時間的時候掛了 (redis lua腳本[同時成功,同時失敗])
- 誤刪鎖
(給每個鎖相應的id)
- 業(yè)務執(zhí)行時間大于鎖的超時間,業(yè)務沒執(zhí)行完,鎖消失了(續(xù)命)
守護線程,鎖快超時的時候重制超時時間while循環(huán)
代碼如下
public void reduceOrder() {
String localKey = "product_001";
String productKey = "order";
String clientId = UUID.randomUUID().toString(); // 鎖id
try {
lock.lock();
//redis 加鎖 setnx 設(shè)置超時時間
Boolean result = redisTemplate.opsForValue().setIfAbsent(localKey, clientId, 10, TimeUnit.SECONDS);
if (!result) { //true System.out.println("正在搶貨中.....");
return;
}
int order = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(productKey)));
if (order > 0) {
int realorder = order - 1;
redisTemplate.opsForValue().set(productKey, String.valueOf(realorder));
System.out.println("訂單數(shù)-1,剩余訂單數(shù)量:" + realorder);
} else {
System.out.println("庫存數(shù)不足,請及時補充訂單");
}
} catch (Exception e) {
System.out.println("訂單扣除失敗");
e.printStackTrace();
} finally {
//解鎖 判斷是否對應id 解決誤刪鎖
//在判斷的時候可能掛了 應使用lua腳本保證原子行
if (clientId.equals(redisTemplate.opsForValue().get(localKey))) {
redisTemplate.delete(localKey);
}
// System.out.println("解鎖成功");
lock.unlock();
}
}
// }
}
上述代碼中沒有體現(xiàn)到lua腳本和守護線程(還有可重入鎖的問題),后面會直接使用Redission
重新執(zhí)行jmeter進行測試 測試結(jié)果
8081
8082
合計50個訂單,沒有重復扣除