分布式鎖的演進
基本原理
我們可以同時去一個地方“占坑”,如果占到,就執行邏輯,否則就必須等待,直到釋放鎖,“占坑”可以去redis,可以去資料庫,可以去任何大家都能訪問的地方,等待可以自旋的方式,
階段一

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
//階段一
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
//獲取到鎖,執行業務
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
//洗掉鎖,如果在此之前報錯或宕機會造成死鎖
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("快取不命中,準備查詢資料庫,,,");
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;
}
問題: setnx占好了位,業務代碼例外或者程式在頁面程序中宕機,沒有執行洗掉鎖邏輯,這就造成了死鎖
解決: 設定鎖的自動過期,即使沒有洗掉,會自動洗掉
階段二

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if (lock) {
//設定過期時間
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();
}
}
問題: setnx設定好,正要去設定過期時間,宕機,又死鎖了,
解決: 設定過期時間和占位必須是原子的,redis支持使用setnx ex命令,
推薦一個開源免費的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
階段三

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

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//為當前鎖設定唯一的uuid,只有當uuid相同時才會進行洗掉鎖的操作
Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
String lockValue = https://www.cnblogs.com/javastack/archive/2022/10/24/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();
}
}
問題: 如果正好判斷是當前值,正要洗掉鎖的時候,鎖已經過期,別人已經設定到了新的值,那么我們洗掉的是別人的鎖
解決: 洗掉鎖必須保證原子性,使用redis+Lua腳本完成
階段五-最終形態

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 = https://www.cnblogs.com/javastack/archive/2022/10/24/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();
}
}
保證加鎖【占位+過期時間】和洗掉鎖【判斷+洗掉】的原子性,更難的事情,鎖的自動續期,
Spring Boot 基礎就不介紹了,推薦看這個免費教程:
https://github.com/javastacks/spring-boot-best-practice
Redisson
Redisson是一個在Redis的基礎上實作的Java駐記憶體資料網格(In-Memory Data Grid),它不僅提供了一系列的分布式的Java常用物件,還提供了許多分布式服務,
其中包括(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的宗旨是促進使用者對Redis的關注分離(Separation of Concern),從而讓使用者能夠將精力更集中地放在處理業務邏輯上,
更多請參考官方檔案:
https://github.com/redisson/redisson/wiki
來源:https://blog.csdn.net/zhangkaixuan456/article/details/110679617
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2022最新版)
2.勁爆!Java 協程要來了,,,
3.Spring Boot 2.x 教程,太全了!
4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/519114.html
標籤:其他
