1)ローカルキャッシュが問題に直面している
複数のサービスがある場合、各サービスのキャッシュはサービスにのみ使用できるため、各サービスはデータベースに1回クエリを実行する必要があり、データが更新されると、単一のサービスのキャッシュデータのみが更新されます。データの不整合が発生します問題
すべてのサービスが同じredisに移動してデータを取得します。この問題を回避できます
2)分散ロック
分散プロジェクトも高い同時実行性でロックする必要があるが、ローカルロックは現在のサービスしかロックできない場合、現時点では分散ロックが必要です
3)分散ロックの進化
ファンダメンタル
同時に「ピットを占領」する場所に行くことができ、そうすればロジックを実行します。それ以外の場合は、ロックが解除されるまで待つ必要があります。「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はsetnxexコマンドの使用をサポートしています
ステージ3
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();
}
}
ロック[職業+有効期限]とロックの削除[判断+削除]の原子性を確認します。難しいのは、ロックの自動更新です