分散型並行システムでは、データベースとキャッシュ データの一貫性が技術的に困難です。完全な産業グレードの分散トランザクション ソリューションが存在すると仮定すると、データベースとキャッシュ データの一貫性は簡単に解決されますが、実際、分散トランザクションは現時点では未熟です。
データベースとキャッシュ データの整合性ソリューションには、さまざまな声があります。
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ロックのステータスに基づいて後続の操作を実行します。
(2) A にデータベースにクエリを実行して古い値を取得するようリクエストします
(3) B に新しい値をデータベースに書き込むようリクエストします
( 4) B にキャッシュの削除を要求します
(5) A に見つかった古い値をキャッシュに書き込むよう要求します
上記の同時実行性の問題の鍵は、ステップ 3 と 4 の後にステップ 5 が発生することです。オペレーティング システムの中断という不確実な要因から、この状況が発生する可能性があることがわかります。
実際の状況から見ると、Redis へのデータの書き込みは、データベースへのデータの書き込みよりもはるかに時間がかかりません。発生確率は低いですが、それでも発生します。
(1) キャッシュの有効期限を長くします
#キャッシュの有効期限を長くして、ダーティ データが特定の時間範囲内に存在できるようにします。次の同時更新が発生すると、ダーティ データが発生する可能性があります。ダーティデータは定期的に存在します。
(2) 更新とクエリは行ロックを共有します
更新とクエリは行分散ロックを共有し、上記の問題は存在しなくなりました。存在する。読み取りリクエストがロックを取得すると、書き込みリクエストはブロック状態になり (タイムアウトが失敗し、すぐに戻ります)、ステップ 3 の前にステップ 5 が実行されることが保証されます。
(3) キャッシュ削除の遅延
RabbitMQ を使用してキャッシュ削除を遅延し、手順 5 の影響を排除します。非同期メソッドを使用しても、パフォーマンスにはほとんど影響がありません。
データベースには、操作の成功を保証するトランザクション メカニズムがあります。単一の Redis 命令はアトミックですが、結合するとアトミックな特性はありません。具体的には、データベース操作が成功すると、アプリケーションが異常にハングアップし、その結果、Redis キャッシュが削除されませんでした。この問題は、Redis サービスのネットワーク接続がタイムアウトすると発生します。
キャッシュの有効期限が設定されている場合、キャッシュの有効期限が切れる前にダーティ データが常に存在します。有効期限が設定されていない場合、ダーティ データは次回データが変更されるまで存在します。 (データベースのデータが変更されており、キャッシュが更新されていません)
データベースを操作する前に、RabbitMQに遅延キャッシュ削除メッセージを書き込み、データベース操作を実行してキャッシュを実行します。削除操作を行います。コードレベルのキャッシュが正常に削除されたかどうかに関係なく、MQ は保証された操作としてキャッシュを削除します。
以上がJava 同時プログラミングのデータベースとキャッシュ データの整合性スキームは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。