ホームページ  >  記事  >  Java  >  Java でキャッシュの一貫性を確保する方法

Java でキャッシュの一貫性を確保する方法

王林
王林転載
2023-05-02 13:13:161120ブラウズ

オプション 1: キャッシュの更新、データベースの更新

最初にキャッシュが正常に更新されてもデータベースの更新が失敗すると、確実にデータの不整合が発生するため、この方法は簡単に削除できます。

オプション 2: データベースを更新してキャッシュを更新する

このキャッシュ更新戦略は、一般に二重書き込みとして知られています。問題は、データベースを同時に更新するシナリオでは、ダーティ データがキャッシュにフラッシュされました

updateDB();
updateRedis();

例: 2 つの操作の間の後続のリクエストによってデータベースとキャッシュが変更された場合、この時点でキャッシュを更新すると、データはすでに期限切れになります。

Java でキャッシュの一貫性を確保する方法

オプション 3: キャッシュを削除してデータベースを更新します

問題があります:データベースを更新する前に、クエリ要求の場合、ダーティ データはキャッシュにフラッシュされます

deleteRedis();
updateDB();

例:2 つの操作の間にデータ クエリが発生すると、古いデータがキャッシュに入れられます。

Java でキャッシュの一貫性を確保する方法

この解決策では、リクエスト データの不一致が発生します。

更新操作のリクエスト A とクエリ操作の別のリクエスト B がある場合。次に、次の状況が発生します。

  • A に書き込み操作の実行とキャッシュの削除を要求します

  • B にクエリを実行して、キャッシュを削除するよう要求します。キャッシュが存在しません

  • B にデータベースにクエリを実行して古い値を取得するように要求します。

  • # B に古い値をキャッシュに書き込むように要求します

  • A に古い値をキャッシュに書き込むよう要求する 新しい値をデータベースに書き込む

##上記の状況では、不整合が発生します。さらに、キャッシュの有効期限戦略を設定しない場合、データは常にダーティ データになります。

オプション 4: データベースを更新してキャッシュを削除します

問題があります:データベースを更新する前にクエリ要求があり、キャッシュが無効です。データベースがクエリされ、キャッシュが更新されます。データベースのクエリとキャッシュの更新の間にデータベースの更新操作が実行された場合、ダーティ データはキャッシュにフラッシュされます

updateDB();
deleteRedis();

例: データベースとキャッシュの間にクエリがある場合キャッシュ データが更新され、操作中にキャッシュが削除されると、古いデータがキャッシュに置かれます。

Java でキャッシュの一貫性を確保する方法

2 つのリクエストがあり、1 つは A にクエリ操作の実行を要求し、もう 1 つは B に更新操作の実行を要求すると仮定すると、次の状況が発生します。

    キャッシュの有効期限が切れました
  • リクエスト A でデータベースにクエリを実行し、古い値を取得します
  • リクエスト B新しい値をデータベースに書き込む
  • リクエスト B にキャッシュを削除する
  • リクエスト A に見つかった古い値をキャッシュに書き込む
  • #こうなった場合 上記の状況では、確かにダーティデータが発生します。ただし、上記の状況が発生するには先天的な条件があります。つまり、データベース操作の書き込みはデータベース操作の読み取りよりも時間がかかりません。

ただし、データベース読み取り操作の速度ははるかに高速です。書き込み操作よりも

#したがって、この状況は発生しにくくなります。 スキームの比較

スキーム 1 とスキーム 2 の共通の欠点:

データベースの同時更新シナリオでは、ダーティ データがキャッシュされますが、一般に、シナリオが同時に書き込まれる可能性は比較的小さいです。 スレッド セーフティの観点から、ダーティ データが生成されます (例:

スレッド A)データベースを更新しました

  • スレッド B がデータベースを更新しました

  • ##スレッド B がキャッシュを更新しました

  • スレッド B がキャッシュを更新しました

  • #スレッドキャッシュを更新しました
オプション 3 と 4 の共通の欠点:

どちらの順序を採用しても、両方の方法にいくつかの問題があります。 :

    マスター/スレーブの遅延問題: 最初に削除されたか最後に削除されたかに関係なく、データベースのマスター/スレーブの遅延によりダーティ データが生成される可能性があります。
  • キャッシュ削除の失敗: キャッシュの削除に失敗すると、ダーティ データが生成されます。
  • 問題解決のアイデア: 二重削除を遅らせ、以下で紹介する再試行メカニズムを追加します。

キャッシュを更新しますか、それともキャッシュを削除しますか?

    1. キャッシュの更新には一定のメンテナンスコストが必要であり、同時更新では問題が発生します
  • 2. 書き込み読み取りが少ない場合は、読み取り要求がまだ来ておらず、キャッシュが何度も更新されているため、キャッシュの役割を果たしません
  • 3。キャッシュに書き込まれた値が更新されるたびに計算されると、パフォーマンスが無駄になります
キャッシュを削除する利点:

シンプル、低コスト、開発が簡単; 欠点: キャッシュミスの原因キャッシュの更新コストが小さく、読み取りが多く書き込みが少なく、基本的に同時書き込みが存在しない場合は、キャッシュを更新することもできますが、そうでない場合は、キャッシュを削除するのが一般的な方法です。

总结

方案 问题 问题出现概率 推荐程度
更新缓存 -> 更新数据库 为了保证数据准确性,数据必须以数据库更新结果为准,所以该方案绝不可行 不推荐
更新数据库 -> 更新缓存 并发更新数据库场景下,会将脏数据刷到缓存 并发写场景,概率一般 写请求较多时会出现不一致问题,不推荐使用。
删除缓存 -> 更新数据库 更新数据库之前,若有查询请求,会将脏数据刷到缓存 并发读场景,概率较大 读请求较多时会出现不一致问题,不推荐使用
更新数据库 -> 删除缓存 在更新数据库之前有查询请求,并且缓存失效了,会查询数据库,然后更新缓存。如果在查询数据库和更新缓存之间进行了数据库更新的操作,那么就会把脏数据刷到缓存 并发读场景&读操作慢于写操作,概率最小 读操作比写操作更慢的情况较少,相比于其他方式出错的概率小一些。勉强推荐。

推荐方案

延迟双删

采用更新前后双删除缓存策略

public void write(String key,Object data){
  redis.del(key);
     db.update(data);
     Thread.sleep(1000);
     redis.del(key);
 }
  • 先淘汰缓存

  • 再写数据库

  • 休眠1秒,再次淘汰缓存

大家应该评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上即可。

这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

问题及解法:

1、同步删除,吞吐量降低如何处理

将第二次删除作为异步的,提交一个延迟的执行任务

2、解决删除失败的方式:

添加重试机制,例如:将删除失败的key,写入消息队列;但对业务耦合有些严重;

Java でキャッシュの一貫性を確保する方法

延时工具可以选择:

最普通的阻塞Thread.currentThread().sleep(1000);

Jdk调度线程池,quartz定时任务,利用jdk自带的delayQueue,netty的HashWheelTimer,Rabbitmq的延时队列,等等

实际场景

我们有个商品中心的场景,是读多写少的服务,并且写数据会发送MQ通知下游拿数据,这样就需要严格保证缓存和数据库的一致性,需要提供高可靠的系统服务能力。

写缓存策略

  • 缓存key设置失效时间

  • 先DB操作,再缓存失效

  • 写操作都标记key(美团中间件)强制走主库

  • 接入美团中间件监听binlog(美团中间件)变化的数据在进行兜底,再删除缓存

Java でキャッシュの一貫性を確保する方法

读缓存策略

  • 先判断是否走主库

  • 如果走主库,则使用标记(美团中间件)查主库

  • 如果不是,则查看缓存中是否有数据

  • 缓存中有数据,则使用缓存数据作为结果

  • 如果没有,则查DB数据,再写数据到缓存

Java でキャッシュの一貫性を確保する方法

注意

关于缓存过期时间的问题

如果缓存设置了过期时间,那么上述的所有不一致情况都只是暂时的。

但是如果没有设置过期时间,那么不一致问题就只能等到下次更新数据时解决。

所以一定要设置缓存过期时间

以上がJava でキャッシュの一貫性を確保する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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