キャッシュブレークダウンの問題を解決するためのロック

1つは、ローカルロックの問題を追加する

1.1ローカルロック分析

以前のRedisキャッシュ導入に続いて、キャッシュの浸透(空の結果キャッシュ)とキャッシュアバランシェの問題(ランダムな値の追加)を簡単に解決できます。キャッシュの内訳の問題には、ロック方法を使用できますが、このロックはどのような種類ですかロックの使用が必要であり、このロックを追加する方法も非常に特殊です。不注意はさまざまな問題を引き起こす可能性があります。

ここでは、最初にローカルロックを追加する場合のいくつかの問題をテストします。

    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //给缓存中放json字符串,拿出的json字符串,还要逆转为能用的对象类型;【序列化与反序列化】

        /**
         * 1.空结果缓存:解决缓存穿透
         * 2.设置过期时间(加随机值): 解决缓存雪崩
         * 3.加锁: 解决缓存击穿
         */
        //1. 加入缓存逻辑,缓存中存的数据是json字符串
        //JSON好处:跨语言,跨平台兼容
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isEmpty(catalogJSON)) {
            //2.缓存中没有,查询数据库
            System.out.println("缓存不命中...查询数据库...");
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            //每次调用这个方法,都有查询数据库,得到返回值,效率非常低下,我们需要加入缓存
            //3. 查到的数据再放入缓存,将对象转为json放在缓存中
            String s = JSON.toJSONString(catalogJsonFromDb);
            redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);
            return catalogJsonFromDb;
        }
        System.out.println("缓存命中...直接返回...");
        //从缓存中获取的JSON,再转为我们指定的对象返回
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
        return result;
    }

    //从数据库查询并封装分类数据
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {

        //只要是同一把锁,就能锁住需要这个锁的所有线程,使用this,this代表的就是当前对象
        //1.synchronized(this):SpringBoot所有的组件在容器中都是单例的
        /**
         * 100万个请求同时进来,进来以后就先锁住,接下来这100万个请求就来竞争锁,假设有一个竞争上来了,那他就去执行数据库查询,查询完以后返回,释放锁
         * 别的请求再一进来,再去查数据库就是不合理的,相当于我们虽然锁住了,相当于再去排队查数据库。
         * 所以拿到锁以后,进来要做的第一件事,就是再看一下缓存里面有没有,如果有了说明是上一个人执行完放好的,如果没有你才需要再去查
         */
        //TODO 本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有,必须使用分布式锁
        synchronized (this) {
            //得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
            String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
            if (!StringUtils.isEmpty(catalogJSON)) {
                //缓存不为null直接返回
                Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
                return result;
            }
            System.out.println("查询了数据库....");
            //将数据库的多次查询变为一次
            Map<String, List<Catelog2Vo>> parentCid  = selectFromDB();
            return parentCid;
        }
    }

この方法でロックするのは適切ですか?

単一のアプリケーションの場合、つまり、プロジェクトはTomatとサーバーにのみデプロイされるため、ロックしても問題ありません。ただし、配布すると変更されます

分散されている場合、私たちの一般的な状況は、負荷分散メカニズムのために、大きな同時実行性を想定して、多くのサーバーにサービスを配置することです。現在、サーバーはなく、すべてが10,000の同時実行性を受け入れます。これをロックして追加します。これは、現在のインスタンスを意味します。オブジェクト、これが同期コードブロックに追加したものであろうと、メソッド上のこれであろうと、これはロックとしての現在のインスタンスであり、現在のインスタンスはコンテナ内の単一のインスタンスですが、1つのプロジェクト、1つのコンテナ、つまり、1つの商品サービスに対して1つのコンテナーがあります。このように、1つの商品サービスに対して8つのマシンがあり、これは8つのマシンに相当します。8つのコンテナーは8つのインスタンスに相当します。したがって、これはそれぞれコンテナーのみを表します。現在のインスタンスのオブジェクト。つまり、これはそれぞれ異なるロックであり、最後に8つのロックを追加することに相当します。

結局、結果として生じる現象は、私たちの商品とサービスのこのロックは10,000リクエストをロックすることと同等であり、1つだけが入力されます。次に、サーバー2も10,000をロックすることと同等であり、1つだけが入力されます。結局、分散型の場合、それは数台のマシンに相当し、いくつかのスレッドをに入れます。これは、データベース内の同じデータをチェックするために、8つのスレッドが同時に入ってくることと同じです。

したがって、ローカルロックは現在のプロセスのみをロックできると述べました。大量の同時数百万のリクエストを実際に実装し、データベースのクエリに1つだけ残したい場合は、分散ロックを使用する必要があります。分散ロックの欠点は、パフォーマンスが比較的遅いことですが、現在のローカルロックはわずかに高速です。ただし、ローカルロックの欠点は、分散状況ではすべてのサービスをロックできないことです。

ただし、シナリオに基づいて、同期ロックを使用することもできます。製品サービスに100ユニットを配置しても、同時実行数は100万になります。データベースをチェックするために最大で100ユニットを配置します。では、どうしますか?データベースの圧力はそれほど大きくなく、ロックは設計ほど重くありません。

1.2ローカルロック圧力テスト

1.2.1問題の提示

1.最初にredisのキーを削除します

必要な効果は、コンソールに出力されると、キャッシュが失われ、データがチェックされ、データベースは1回しかチェックできないということです。複数回チェックされた場合、ロックは失敗します。

2.圧力テスト

 3.テスト結果

4.結果分析

図に示すように、黒はロックされた部分を示します。スレッド1はロックを解除するために終了します。ロックが解除された後、結果はキャッシュに入れられていません。スレッド2はロックを取得して入りました。最初にキャッシュにデータがあるかどうかを確認する必要がありますが、スレッド1はデータをredisに入れます。これはネットワークの相互作用であり、非常に遅い可能性があります。その結果、データ2はそうではありません。キャッシュで見つかりました。データ、そしてデータベースもありません。したがって、これにより、データベースに2回クエリが実行されます。

1.2.2問題解決

この問題を解決するために、結果をキャッシュ操作に入れることができます。ロックを解除した後は行わないでください。データベースが見つかった場合は、結果がキャッシュに入れられないようにします。ロックされ、データベースに2回クエリを実行します。

    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //给缓存中放json字符串,拿出的json字符串,还要逆转为能用的对象类型;【序列化与反序列化】

        /**
         * 1.空结果缓存:解决缓存穿透
         * 2.设置过期时间(加随机值): 解决缓存雪崩
         * 3.加锁: 解决缓存击穿
         */
        //1. 加入缓存逻辑,缓存中存的数据是json字符串
        //JSON好处:跨语言,跨平台兼容
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isEmpty(catalogJSON)) {
            //2.缓存中没有,查询数据库
            System.out.println("缓存不命中...查询数据库...");
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            //每次调用这个方法,都有查询数据库,得到返回值,效率非常低下,我们需要加入缓存
            return catalogJsonFromDb;
        }
        System.out.println("缓存命中...直接返回...");
        //从缓存中获取的JSON,再转为我们指定的对象返回
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
        return result;
    }

    //从数据库查询并封装分类数据
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {

        //只要是同一把锁,就能锁住需要这个锁的所有线程,使用this,this代表的就是当前对象
        //1.synchronized(this):SpringBoot所有的组件在容器中都是单例的
        /**
         * 100万个请求同时进来,进来以后就先锁住,接下来这100万个请求就来竞争锁,假设有一个竞争上来了,那他就去执行数据库查询,查询完以后返回,释放锁
         * 别的请求再一进来,再去查数据库就是不合理的,相当于我们虽然锁住了,相当于再去排队查数据库。
         * 所以拿到锁以后,进来要做的第一件事,就是再看一下缓存里面有没有,如果有了说明是上一个人执行完放好的,如果没有你才需要再去查
         */
        //TODO 本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有,必须使用分布式锁
        synchronized (this) {
            //得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
            String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
            if (!StringUtils.isEmpty(catalogJSON)) {
                //缓存不为null直接返回
                Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
                return result;
            }
            System.out.println("查询了数据库....");
            //将数据库的多次查询变为一次
            Map<String, List<Catelog2Vo>> parentCid  = selectFromDB();
            //3. 查到的数据再放入缓存,将对象转为json放在缓存中
            String s = JSON.toJSONString(parentCid);
            redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);
            return parentCid;
        }
    }

したがって、データをチェックし、チェック後にキャッシュを配置する必要があります。これはアトミック操作であり、同じロックで実行されます。そうしないと、ロック全体の解放のタイミングの問題が発生し、 2つのデータベースをチェックするように導きました。

1.3分散状況でのローカルロックの問題

1.3.1商品とサービスの2つのコピーを作成する

1.3.2Jmeter圧力テスト

リクエストはnginxからゲートウェイに転送され、ゲートウェイはいくつかの商品やサービスに負荷分散されます

データベースはサービスごとにクエリされます。前述のように、ローカルロックを使用する場合、すべてのマイクロサービスをロックすることはできません。ローカルロックは現在のサービスのみをロックできます。すべてのサービスをロックするには、分散ロックを追加する必要があります。

ビデオチュートリアル

おすすめ

転載: blog.csdn.net/qq_38826019/article/details/115026820