ホームページ  >  記事  >  Java  >  Java 同時プログラミングのデータベースとキャッシュ データの整合性スキームは何ですか?

Java 同時プログラミングのデータベースとキャッシュ データの整合性スキームは何ですか?

PHPz
PHPz転載
2023-04-24 08:28:06876ブラウズ

1. はじめに

分散型並行システムでは、データベースとキャッシュ データの一貫性が技術的に困難です。完全な産業グレードの分散トランザクション ソリューションが存在すると仮定すると、データベースとキャッシュ データの一貫性は簡単に解決されますが、実際、分散トランザクションは現時点では未熟です。

2. さまざまな声

データベースとキャッシュ データの整合性ソリューションには、さまざまな声があります。

  • # 最初にデータベースを操作してからキャッシュを操作するか、最初にキャッシュしてからデータベースを操作してください

  • キャッシュを更新または削除する必要があります

1. 操作のシーケンス

同時システムでは、データベースとキャッシュの二重書き込みシナリオでは、より高い同時実行性を追求するために、データベースとキャッシュに対する操作が実行されます。明らかに同時に実行されません。前者の操作は成功し、後者は非同期で実行されます。

成熟した産業用データ ストレージ ソリューションとして、リレーショナル データベースには完全なトランザクション処理メカニズムが備わっています。データがディスクに配置されると、ハードウェア障害に関係なく、データは責任を持って維持されると言えます。失った。

いわゆるキャッシュはメモリに保存されたデータにすぎず、サービスが再起動されると、キャッシュされたデータはすべて失われます。これはキャッシュと呼ばれるため、キャッシュされたデータが失われることに常に備えてください。 Redis には永続化メカニズムがありますが、100% の永続性を保証できますか? Redis はデータをディスクに非同期的に保存します。キャッシュはキャッシュであり、データベースはデータベースです。これらは 2 つの異なるものです。キャッシュをデータベースとして使用することは非常に危険です。

データセキュリティの観点から、最初にデータベースが操作され、次にキャッシュが非同期に操作されてユーザーのリクエストに応答します。

2. キャッシュを扱うときの考え方

キャッシュが更新されるか削除されるかは、遅延スタイルとフル スタイルに対応します。スレッド セーフの観点から、キャッシュ操作の削除は比較的簡単です。難しい。キャッシュを削除することを前提としてクエリのパフォーマンスが満たされる場合は、キャッシュを削除することが推奨されます。

キャッシュを更新するとクエリ効率は向上しますが、スレッドによるダーティデータの同時実行は処理が面倒です。序文ではMQなど他のメッセージミドルウェアを紹介していますので、必要な場合以外は推奨しません。

3. スレッドの同時実行性の分析

スレッドの同時実行によって引き起こされる問題を理解するための鍵は、まずシステムの割り込みを理解することです。オペレーティング システムがタスクをスケジュールしているとき、いつでも割り込みが発生します。これは、スレッドデータの不一致が原因です。 4 スレッドと 8 スレッドの CPU を例にとると、最大 8 スレッドを同時に処理できますが、オペレーティング システムは 8 スレッドをはるかに超える数のスレッドを管理するため、スレッドは一見並列的に処理されます。

データのクエリ

1. 非同時実行環境
非同時実行環境では、次の方法を使用してデータをクエリしても問題はありません: 最初にキャッシュをクエリします。 、キャッシュされたデータが存在しない場合は、データベースにクエリを実行し、キャッシュを更新し、結果を返します。

public BuOrder getOrder(Long orderId) {
    String key = ORDER_KEY_PREFIX + orderId;
    BuOrder buOrder = RedisUtils.getObject(key, BuOrder.class);
    if (buOrder != null) {
        return buOrder;
    }
    BuOrder order = getById(orderId);
    RedisUtils.setObject(key, order, 5, TimeUnit.MINUTES);
    return order;
}

高同時実行環境に重大な欠陥がある場合: キャッシュに障害が発生すると、大量のクエリ リクエストが流入し、すべてが瞬時に DB に到達します。データベース接続リソースが枯渇する可能性があります。クライアントは 500 エラーで応答します。深刻な場合には、データベースに過剰な負荷がかかり、サービスがシャットダウンされる可能性があります。

2. 同時環境
したがって、同時環境では、上記のコードを変更する必要があり、分散ロックが使用されます。大量のリクエストが流入すると、ロックを取得したスレッドはデータベースにアクセスしてデータをクエリする機会があり、残りのスレッドはブロックされます。データがクエリされ、キャッシュが更新されると、ロックが解放されます。待機中のスレッドはキャッシュを再チェックし、データを取得できることを確認し、キャッシュされたデータに直接応答します。

ここでは分散ロックについて言及していますが、テーブル ロックと行ロックを使用する必要がありますか?分散行ロックを使用して同時実行性を高め、二次チェック メカニズムを使用して、ロックの取得を待機しているスレッドがすぐに結果を返せるようにします

#

@Override
public BuOrder getOrder(Long orderId) {
    /* 如果缓存不存在,则添加分布式锁更新缓存 */
    String key = ORDER_KEY_PREFIX + orderId;
    BuOrder order = RedisUtils.getObject(key, BuOrder.class);
    if (order != null) {
        return order;
    }
    String orderLock = ORDER_LOCK + orderId;
    RLock lock = redissonClient.getLock(orderLock);
    if (lock.tryLock()) {
        order = RedisUtils.getObject(key, BuOrder.class);
        if (order != null) {
            LockOptional.ofNullable(lock).ifLocked(RLock::unlock);
            return order;
        }
        BuOrder buOrder = getById(orderId);
        RedisUtils.setObject(key, buOrder, 5, TimeUnit.MINUTES);
        LockOptional.ofNullable(lock).ifLocked(RLock::unlock);
    }
    return RedisUtils.getObject(key, BuOrder.class);
}

データの更新

#1. 非同時実行環境
非同時実行環境では、次のコードによりデータの不整合 (データが上書きされる) が発生する可能性があります。データベース レベルのオプティミスティック ロックを使用すると、データが上書きされる問題は解決できますが、無効な更新トラフィックが引き続きデータベースに流れます。

public Boolean editOrder(BuOrder order) {
    /* 更新数据库 */
    updateById(order);
    /* 删除缓存 */
    RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId());
    return true;
}

2. 同時環境
上記の分析でデータベースのオプティミスティック ロックを使用すると、同時更新でデータが上書きされる問題を解決できますが、同じレコード行が変更されると、バージョン番号が変更されるため、データベースに流れる後続の同時リクエストは無効なトラフィックになります。データベースの負荷を軽減するための主な戦略は、データベースの前に無効なトラフィックを遮断することです。

分散ロックを使用すると、同時トラフィックが順序よくデータベースにアクセスできるようになります。データベース レベルでオプティミスティック ロックが使用されていることを考慮すると、ロックを取得した 2 番目以降のスレッドはデータベースを無効なトラフィックとして操作します。

スレッドは、ロックを取得するときにタイムアウト終了戦略を採用します。ロックを待機しているスレッドはタイムアウトしてすぐに終了し、ユーザー要求にすぐに応答して、データ更新操作を再試行します。

public Boolean editOrder(BuOrder order) {
    String orderLock = ORDER_LOCK + order.getOrderId();
    RLock lock = redissonClient.getLock(orderLock);
    try {
        /* 超时未获取到锁,快速失败,用户端重试 */
        if (lock.tryLock(1, TimeUnit.SECONDS)) {
            /* 更新数据库 */
            updateById(order);
            /* 删除缓存 */
            RedisUtils.deleteObject(OrderServiceImpl.ORDER_KEY_PREFIX + order.getOrderId());
            /* 释放锁 */
            LockOptional.ofNullable(lock).ifLocked(RLock::unlock);
            return true;
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return false;
}

依存環境

上記のコードでは、ロックをカプセル化するツール クラスを使用しています。

<dependency>
  <groupId>xin.altitude.cms</groupId>
  <artifactId>ucode-cms-common</artifactId>
  <version>1.4.3.2</version>
</dependency>

LockOptionalロックのステータスに基づいて後続の操作を実行します。

4. 最初にデータベース、次にキャッシュ

データの一貫性

1. 問題の説明
次に、データベースの更新に同時実行性があるかどうかについて説明します。まずキャッシュを削除してください。

(1) キャッシュの有効期限が切れたばかりです

(2) A にデータベースにクエリを実行して古い値を取得するようリクエストします
(3) B に新しい値をデータベースに書き込むようリクエストします
( 4) B にキャッシュの削除を要求します
(5) A に見つかった古い値をキャッシュに書き込むよう要求します

上記の同時実行性の問題の鍵は、ステップ 3 と 4 の後にステップ 5 が発生することです。オペレーティング システムの中断という不確実な要因から、この状況が発生する可能性があることがわかります。

2. 解決策

実際の状況から見ると、Redis へのデータの書き込みは、データベースへのデータの書き込みよりもはるかに時間がかかりません。発生確率は低いですが、それでも発生します。

  • (1) キャッシュの有効期限を長くします

#キャッシュの有効期限を長くして、ダーティ データが特定の時間範囲内に存在できるようにします。次の同時更新が発生すると、ダーティ データが発生する可能性があります。ダーティデータは定期的に存在します。

  • (2) 更新とク​​エリは行ロックを共有します

更新とクエリは行分散ロックを共有し、上記の問題は存在しなくなりました。存在する。読み取りリクエストがロックを取得すると、書き込みリクエストはブロック状態になり (タイムアウトが失敗し、すぐに戻ります)、ステップ 3 の前にステップ 5 が実行されることが保証されます。

  • (3) キャッシュ削除の遅延

RabbitMQ を使用してキャッシュ削除を遅延し、手順 5 の影響を排除します。非同期メソッドを使用しても、パフォーマンスにはほとんど影響がありません。

特殊なケース

データベースには、操作の成功を保証するトランザクション メカニズムがあります。単一の Redis 命令はアトミックですが、結合するとアトミックな特性はありません。具体的には、データベース操作が成功すると、アプリケーションが異常にハングアップし、その結果、Redis キャッシュが削除されませんでした。この問題は、Redis サービスのネットワーク接続がタイムアウトすると発生します。

キャッシュの有効期限が設定されている場合、キャッシュの有効期限が切れる前にダーティ データが常に存在します。有効期限が設定されていない場合、ダーティ データは次回データが変更されるまで存在します。 (データベースのデータが変更されており、キャッシュが更新されていません)

解決策

データベースを操作する前に、RabbitMQに遅延キャッシュ削除メッセージを書き込み、データベース操作を実行してキャッシュを実行します。削除操作を行います。コードレベルのキャッシュが正常に削除されたかどうかに関係なく、MQ は保証された操作としてキャッシュを削除します。

以上がJava 同時プログラミングのデータベースとキャッシュ データの整合性スキームは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。