分散ロックの進化
基本的
同時に「ピットを占有する」場所に行くことができ、そこを占領したらロジックを実行します。それ以外の場合は、ロックが解除されるまで待つ必要があります。「Zhankeng」は、redis、データベース、および誰もがアクセスできる場所であればどこにでもアクセスできます。回転する方法を待っています。
ステージ 1
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;
}
質問: 1. setnx が位置を占有しているか、ビジネス コードが異常であるか、ページ処理中にプログラムがクラッシュします。削除ロック ロジックが実行されないため、デッドロックが発生します。
解決策: ロックの自動有効期限を設定します。ロックが削除されていなくても、自動的に削除されます。
ステージ2
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();
}
質問: 1. setnx が設定され、有効期限を設定しようとすると、システムがクラッシュします。また行き詰まりました。解決策: 有効期限とプレースホルダーの設定はアトミックである必要があります。redis は setnx ex コマンドの使用をサポートします
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();
}
}
質問: 1. ロックを直接削除しますか? ? ? 営業時間が長くなりロック自体の有効期限が切れた場合は弊社が直接削除しますし、他の方が保持しているロックを削除することも可能です。解決策: ロックを占有する場合、値は uuid として指定され、各ユーザーはロックを削除する前に自分のロックと一致します。
ステージ 4
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 = 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. たまたま現在の値であると判断された場合、ロックが削除されようとしているときに、ロックの有効期限が切れており、他の誰かがロックを新しい値に設定しています。その場合、私たちが削除するのは他の誰かのロック ソリューションです。ロックの削除は原子性を確保する必要があります。redis+Luaスクリプトを使用して完了
ステージ 5 - 最終形態
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();
}
}
4) 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 フィルター、リモート サービス、Spring キャッシュ、Executor サービス、Live Object サービス) が含まれます。 、スケジューラー サービス) Redisson は、Redis を使用する最も簡単で便利な方法を提供します。Redisson の目的は、ユーザーがビジネス ロジックの処理により集中できるように、Redis からユーザーの関心事の分離 (Separation of Concern) を促進することです。