分散キャッシュ(一般的な問題と解決策)

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();
        }
    }

ロック[職業+有効期限]とロックの削除[判断+削除]の原子性を確認します。難しいのは、ロックの自動更新です

おすすめ

転載: blog.csdn.net/songyinyi/article/details/113756432