Redis 戦闘 - データをキャッシュし、キャッシュとデータベースのデータの整合性を解決します

キャッシュの定義

キャッシュ ( Cache ) は、データ交換用のバッファです。一般的に知られているキャッシュは、バッファ内のデータであり、一般にデータベースから取得され、ローカル コードに格納されます。システムに過剰なデータアクセスが殺到し、操作スレッドが情報処理が間に合わず麻痺することを防ぐことは、企業にとって致命的であり、実際の開発における製品の評判やユーザーの評価にとっても致命的であるため、企業はキャッシュ技術、redisを非常に重視しています最も一般的に使用されるキャッシュ ミドルウェアは、面接のための高頻度のテスト サイトでもあります。

キャッシュの使用目的

キャッシュ データはコードに保存され、コードはメモリ内で実行されます。メモリの読み取りおよび書き込みパフォーマンスは、ディスクのパフォーマンスよりもはるかに高くなります。キャッシュにより、サーバーに対する読み取りおよび書き込みの負荷が大幅に軽減さます同時ユーザーアクセス。実際の開発プロセスでは、企業内のデータ量は数十万から数千万に及びますが、このような大量のデータに対してキャッシュがなければシステムは対応できません。キャッシュテクノロジーの数。

キャッシュの使い方

実際の開発では、システムの速度をさらに向上させるためにマルチレベル キャッシュが構築されます。たとえば、ローカル キャッシュと Redis 内のキャッシュが同時に使用されます。

ブラウザキャッシュ:主にブラウザ側に存在するキャッシュ

アプリケーション層キャッシュ:前述のマップなどの Tomcat ローカル キャッシュに分割することも、キャッシュとして Redis を使用することもできます

データベース キャッシュ:データベースにはバッファ プールとして領域があり、追加、変更、チェックされたデータは最初に mysql キャッシュにロードされます。

CPU キャッシュ:現代のコンピュータの最大の問題は、CPU の性能は向上しましたが、メモリの読み書き速度が追いついていないことです。そのため、現在の状況に適応するために、CPU の L1、L2、L3 キャッシュには、追加されました。

キャッシュストア情報

 ストア情報インターフェイスは同時実行性が高く、毎回データベースからクエリ データをクエリすることはできません。高い同時実行性に対処するには、ストア データを Redis にキャッシュする必要があります。

@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
    //这里是直接查询数据库
    return shopService.queryById(id);
}

キャッシュモデルとアイデア

標準的な操作方法は、データベースにクエリを実行する前にキャッシュにクエリを実行することです。キャッシュされたデータが存在する場合は、キャッシュから直接返されます。キャッシュされたデータが存在しない場合は、再度データベースにクエリを実行してから、データを Redis に保存します。

 コード

なお、このときキャッシュデータには有効期限が設定されておらず、データベースへの負担を軽減するためにはキャッシュをメモリ上に常駐させる必要がありますが、キャッシュデータとキャッシュデータの不整合が発生するという問題も発生します。これは、バッファ更新戦略の問題につながります

@Override
    public Result queryById(Long id) {
        //根据业务代码组装key
        String key = CACHE_SHOP_KEY + id;
        //从redis中获取商铺信息
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)) {
            //将json转化为shop对象直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);

        }
        Shop shop = getById(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        //将数据库查询的数据写入缓存
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
        //返回
        return Result.ok(shop);

キャッシュ更新戦略

更新戦略は主にビジネスに応じて選択されます。このプロジェクトでは、アクティブ更新 + タイムアウト除去の更新戦略が採用されています。タイムアウト除去は主に、キャッシュが随時更新されるようにするための保証された更新戦略として使用されます。アクティブな更新をトリガーせずにキャッシュをクリアします

キャッシュ更新は、主にメモリ データが貴重なため、メモリを節約するために Redis によって設計されたものです。Redis に大量のデータを挿入すると、キャッシュ内のデータが過剰になる可能性があるため、Redis は一部のデータ更新を処理します。彼を排除したと呼ぶ方が適切だ。

メモリの削除: Redis は自動です。Redis メモリが設定した max-memery に達すると、重要でないデータを削除するための削除メカニズムが自動的にトリガーされます(戦略は自分で設定できます)。

タイムアウトの削除: Redis の有効期限 ttl を設定すると、キャッシュを引き続き使用できるように、Redis はタイムアウトしたデータを削除します。

アクティブな更新:キャッシュを削除するメソッドを手動で呼び出すことができます。これは通常、キャッシュとデータベース間の不整合の問題を解決するために使用されます。

 データベースキャッシュデータの不整合の解決策

キャッシュのデータ ソースはデータベースから取得されデータベース内のデータは変更されるため、データベース内のデータが変更されてもキャッシュが同期されていない場合、この時点で一貫性の問題が発生し、その結果は次のようになります。 :

ユーザーがキャッシュ内の古いデータを使用すると、同様のマルチスレッド データ セキュリティの問題が発生し、ビジネスや製品の評判などに影響を及ぼします。これを解決するにはどうすればよいでしょうか? いくつかのオプションがあります

キャッシュ アサイド パターンの手動コーディング方法: キャッシュ呼び出し元はデータベースを更新した後にキャッシュを更新します。二重書き込みスキームとも呼ばれます。

リード/ライトスルーパターン:システム自体で完了し、データベースとキャッシュの問題はシステム自体で処理されます。

ライトビハインドキャッシュパターン:呼び出し元はキャッシュを操作するだけで、他のスレッドはデータベースを非同期に処理して最終的な整合性を実現します。

オプション 2 を使用すると、システムの複雑さが増大し、呼び出し元が関連する問題のトラブルシューティングを行うのに役立ちません。オプション 3 では、一連のスレッド セーフが必要となり、データベース キャッシュの不整合が生じます。総合的に考慮した結果、手動を選択する方が安全です。コーディング

手動コーディングステップ

  1. キャッシュの削除:データベース更新時にキャッシュを無効にし、クエリ時にキャッシュを更新します(データベース更新と同時にキャッシュも更新すると、更新操作が多すぎてパフォーマンスが保証できません)
  2. モノリシック システムでは、キャッシュとデータベース操作を 1 つのトランザクションに入れて、データベースが正常に更新されたときにキャッシュも正常に追加されるようにします。つまり、2 つの操作が同時に成功するか失敗するかを確認します
  3. データベースを操作してからキャッシュを削除します マルチスレッドの場合、データベースの操作時間は Redis キャッシュの操作時間よりもはるかに長くなります データベースの書き込み時にキャッシュが失敗する可能性は低いです。

ストア、キャッシュ、データベース間の二重書き込みの一貫性を実現します。

  • ID に基づいてストアをクエリするときに、キャッシュが見つからない場合は、データベースにクエリを実行し、データベースの結果をキャッシュに書き込み、タイムアウトを設定します。
  • ID に基づいてストアを変更する場合は、まずデータベースを変更してからキャッシュを削除します

キャッシュを追加するときに Redis キャッシュを設定するときに有効期限を追加します

@Override
    public Result queryById(Long id) {
        //根据业务代码组装key
        String key = CACHE_SHOP_KEY + id;
        //从redis中获取商铺信息
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)) {
            //将json转化为shop对象直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);

        }
        Shop shop = getById(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        //将数据库查询的数据写入缓存,并设置过期时间
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L,TimeUnit.MINUTES);
        //返回
        return Result.ok(shop);
    }

二重書き込み問題を解決するために、削除戦略を使用することにしました。データを変更する場合、キャッシュ内のデータを削除します。クエリ時にキャッシュにデータがないことが判明した場合、最新のデータがロードされます。 mysql から取得することで、データベースのキャッシュとの不整合を回避します。トランザクションを宣言するには、このメソッドに @Transactional アノテーションを付ける必要があります。

@Transactional
@Override
public Result update(Shop shop) {
    Long id = shop.getId();
    //判断id是否为空,因为可以绕过前端直接发送请求,此步必须判断
    if (id == null) {
        return Result.fail("店铺id不能为空");
    }
    //更新数据库
    updateById(shop);
    //删除缓存
    stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
    return Result.ok();
}

おすすめ

転載: blog.csdn.net/weixin_64133130/article/details/132378548