RedisTemplate 分布式鎖演變,Redission 分布式鎖實現(xiàn)!
點擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”

來源 | blog.csdn.net/zhangkaixuan456/article/details/110679617
基本原理 階段一 階段二 階段三 階段四 階段五-最終形態(tài) 4) Redisson
分布式鎖的演進(jìn)
基本原理
我們可以同時去一個地方“占坑”,如果占到,就執(zhí)行邏輯。否則就必須等待,直到釋放鎖。“占坑”可以去redis,可以去數(shù)據(jù)庫,可以去任何大家都能訪問的地方。等待可以自旋的方式。
階段一

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
//階段一
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
//獲取到鎖,執(zhí)行業(yè)務(wù)
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
//刪除鎖,如果在此之前報錯或宕機(jī)會造成死鎖
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è)置鎖的自動過期,即使沒有刪除,會自動刪除
階段二

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if (lock) {
//設(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è)置過期時間,宕機(jī)。又死鎖了。解決:設(shè)置過期時間和占位必須是原子的。redis支持使用setnx ex命令
階段三

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
//加鎖的同時設(shè)置過期時間,二者是原子性操作
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
//模擬超長的業(yè)務(wù)執(zhí)行時間
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ù)時間很長,鎖自己過期了,我們直接刪除,有可能把別人正在持有的鎖刪除了。解決:占鎖的時候,值指定為uuid,每個人匹配是自己的鎖才刪除。
階段四

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//為當(dāng)前鎖設(shè)置唯一的uuid,只有當(dāng)uuid相同時才會進(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)前值,正要刪除鎖的時候,鎖已經(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();
}
}
保證加鎖【占位+過期時間】和刪除鎖【判斷+刪除】的原子性。更難的事情,鎖的自動續(xù)期
4) Redisson
Redisson是一個在Redis的基礎(chǔ)上實現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務(wù)。
其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)
Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進(jìn)使用者對Redis的關(guān)注分離(Separation of Concern),從而讓使用者能夠?qū)⒕Ω械胤旁谔幚順I(yè)務(wù)邏輯上。
更多請參考官方文檔: https://github.com/redisson/redisson/wiki
干貨分享
最近將個人學(xué)習(xí)筆記整理成冊,使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)取!
?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動設(shè)計速成)》?007:全部?008:加技術(shù)群討論
加個關(guān)注不迷路
喜歡就點個"在看"唄^_^
