階段一
圖片
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
//階段一
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
//獲取到鎖离钝,執(zhí)行業(yè)務(wù)
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
//刪除鎖票编,如果在此之前報(bào)錯(cuò)或宕機(jī)會(huì)造成死鎖
stringRedisTemplate.delete("lock");
return categoriesDb;
}else {
//沒獲取到鎖,等待100ms重試
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonDbWithRedisLock();
}
}
public Map<String, List<Catalog2Vo>> getCategoryMap() {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
String catalogJson = ops.get("catalogJson");
if (StringUtils.isEmpty(catalogJson)) {
System.out.println("緩存不命中卵渴,準(zhǔn)備查詢數(shù)據(jù)庫。鲤竹。浪读。");
Map<String, List<Catalog2Vo>> categoriesDb= getCategoriesDb();
String toJSONString = JSON.toJSONString(categoriesDb);
ops.set("catalogJson", toJSONString);
return categoriesDb;
}
System.out.println("緩存命中。辛藻。碘橘。。");
Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
return listMap;
}
問題:1吱肌、setnx占好了位痘拆,業(yè)務(wù)代碼異常或者程序在頁面過程中宕機(jī)氮墨。沒有執(zhí)行刪除鎖邏輯纺蛆,這就造成了死鎖
解決:設(shè)置鎖的自動(dòng)過期,即使沒有刪除规揪,會(huì)自動(dòng)刪除
階段二
圖片
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if (lock) {
//設(shè)置過期時(shí)間
stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
stringRedisTemplate.delete("lock");
return categoriesDb;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonDbWithRedisLock();
}
}
問題:1桥氏、setnx設(shè)置好,正要去設(shè)置過期時(shí)間猛铅,宕機(jī)字支。又死鎖了。解決:設(shè)置過期時(shí)間和占位必須是原子的。redis支持使用setnx ex命令
階段三
圖片
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
//加鎖的同時(shí)設(shè)置過期時(shí)間堕伪,二者是原子性操作
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
//模擬超長的業(yè)務(wù)執(zhí)行時(shí)間
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringRedisTemplate.delete("lock");
return categoriesDb;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonDbWithRedisLock();
}
}`
問題:1揖庄、刪除鎖直接刪除?欠雌?蹄梢?如果由于業(yè)務(wù)時(shí)間很長,鎖自己過期了桨昙,我們直接刪除检号,有可能把別人正在持有的鎖刪除了。解決:占鎖的時(shí)候蛙酪,值指定為uuid齐苛,每個(gè)人匹配是自己的鎖才刪除。
階段四
圖片
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//為當(dāng)前鎖設(shè)置唯一的uuid桂塞,只有當(dāng)uuid相同時(shí)才會(huì)進(jìn)行刪除鎖的操作
Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
String lockValue = ops.get("lock");
if (lockValue.equals(uuid)) {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringRedisTemplate.delete("lock");
}
return categoriesDb;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonDbWithRedisLock();
}
}`
問題:1凹蜂、如果正好判斷是當(dāng)前值,正要?jiǎng)h除鎖的時(shí)候阁危,鎖已經(jīng)過期玛痊,別人已經(jīng)設(shè)置到了新的值。那么我們刪除的是別人的鎖解決:刪除鎖必須保證原子性狂打。使用redis+Lua腳本完成
階段五-最終形態(tài)
圖片
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
String lockValue = ops.get("lock");
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);
return categoriesDb;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonDbWithRedisLock();
}
}`
保證加鎖【占位+過期時(shí)間】和刪除鎖【判斷+刪除】的原子性擂煞。更難的事情,鎖的自動(dòng)續(xù)期