目次
キャッシュを更新しますか、それともキャッシュを削除しますか?
キャッシュの二重書き込みの整合性とは何ですか?
キャッシュを使用する限り、キャッシュとデータベースの二重ストレージと二重書き込みが発生する可能性があり、二重書き込みを使用する限り、データの一貫性の問題が確実に発生します。Redis がデータベース内のデータと整合性があり、正しいデータを返すことを確認する必要があります。
キャッシュを更新しますか、それともキャッシュを削除しますか?
キャッシュを更新せずに削除する
キャッシュが更新されると、同時書き込み中にデータの不整合が発生する可能性があります。
更新操作に対してリクエスト A とリクエスト B が同時に存在すると仮定すると、次のようになります。
- (1) スレッド A がデータベースを更新しました
- (2) スレッド B がデータベースを更新しました
- (3) スレッド B がキャッシュを更新しました
- (4) スレッド A がキャッシュを更新しました
つまり、A にキャッシュの更新を要求するのは、B にキャッシュの更新を要求するよりも先にする必要がありますが、ネットワークなどの理由により、B が A より先にキャッシュを更新しました。これはデータがダーティになるため、考慮されません。
データが変更されるたびにキャッシュが「無意識に」更新されても、キャッシュ内のデータが「すぐに読み取られる」とは限らない場合、アクセス頻度の低いデータが大量にキャッシュに格納されることになり、キャッシュ リソースが無駄になります。
また、多くの場合、キャッシュに書き込まれる値はデータベース内の値と 1 対 1 に対応していません。最初にデータベースにクエリが実行され、その後、一連の「計算」を通じて値が取得される可能性が非常に高いです。値がキャッシュに書き込まれる前。
最初にキャッシュを削除してからデータベースを更新します
シーンの説明
スレッド A はまず redis 内のデータを正常に削除し、次に mysql を更新しましたが、この時点では mysql は更新中であり、まだ終了していません。(ネットワークの遅延など) B が突然現れ、キャッシュされたデータを読み出します。このとき、redis 内のデータは空になっており、スレッド B が読み出しに来ると、まず redis 内のデータを読み込みます(スレッド A によって削除されています)。
上記のシナリオから生じる問題:
- B は mysql から古い値を取得しました: スレッド B は、それが redis にない (キャッシュが見つからない) ことを発見し、すぐに mysql で読み取りました。古い値はデータベースから読み取られました。
- B は取得した古い値を redis に書き戻します。古い値のデータを取得した後、フロント デスクに戻り、それを redis に書き戻します (スレッド A によって削除されたばかりの古いデータは、再び書き戻される可能性が非常に高くなります)。
最後に: スレッド A が mysql の更新を完了し、redis のキャッシュがダーティ データであることがわかりました。
時間 | スレッドA | スレッドB | 発生する問題 |
t1 | A に書き込み操作をリクエストし、キャッシュを削除した後、作業が進行中です... | A も mysql を更新したため、B は古い値を読み取ってしまいました。
|
|
t2 | キャッシュから読み込めない場合は、すぐに mysal を読み込みますが、A が mysal の更新を完了していないため、古い値が読み込まれます。
|
||
t3 | mysql データベースの値を更新します。 | redis は B によって書き戻された古い値であり 、mysql は A によって更新された新しい値です。 データの不整合の問題があります。 |
解決策: 遅延二重削除戦略
上の図に示すように、最初にキャッシュされたデータを削除してからデータベース業務を処理し、一定期間スリープしてから再度削除することができます。これは遅延二重削除です。
なぜしばらく寝るのですか?
スリープ期間を追加する目的は、スレッド B が最初にデータベースからデータを読み取り、次に不足しているデータをキャッシュに書き込み、その後スレッド A がそのデータを削除できるようにすることです。したがって、スレッド A がスリープする時間は、サイト B がデータを読み取ってキャッシュに書き込む時間よりも長くする必要があります。このように、他のスレッドがデータを読み込むとキャッシュミスが発生するため、最新の値がデータベースから読み取られます。このソリューションは、初めてキャッシュ値を削除した後、一定期間削除を遅らせるため、「遅延二重削除」とも呼ばれます。
直接削除すると、スレッド B が redis に書き込んでいない可能性があります。スレッド A が書き込んだ後、スレッド B が再度書き込んで上書きされます。
睡眠にはどのくらい時間がかかりますか?その時間を決めるにはどうすればよいですか?
- 業務プログラムの実行時に、データの読み込みやキャッシュの書き込みを行うスレッドの動作時間を計測し、プロジェクトのデータ読み込みにかかるビジネスロジックを自分で評価し、見積もってください。データ書き込みのスリープ時間は、データ ビジネス ロジックの読み取りにかかる時間に 100 ミリ秒を加えた時間に基づきます。
- この目的は、読み取りリクエストが確実に終了し、書き込みリクエストによって読み取りリクエストによって発生したキャッシュされたダーティ データを削除できるようにすることです。
まずデータベースを更新してからキャッシュを削除します
シーンの説明
時間 | スレッドA | スレッドB | 発生する問題 |
t1 | データベース内の値を削除する | ||
t2 | キャッシュに即時ヒットがあり、この時点で、B はキャッシュされた古い値を読み取ります。 | A にはキャッシュされた値を削除する時間がなかったため、B のキャッシュ ヒットにより古い値が読み取られました。 | |
t3 | キャッシュされたデータを更新します |
例外の理由: キャッシュの削除が失敗するか遅すぎる場合、redis へのアクセス要求が再度行われたときにキャッシュがヒットし、キャッシュされた古い値が読み取られます。
解決策: リトライ機構 + MQ の導入
- 削除するキャッシュ値や更新するデータベース値をメッセージキューに一時的に保存できます(たとえば、Kafka/RabbitMQ などを使用)。
- プログラムがキャッシュされた値の削除またはデータベース値の更新に失敗した場合、メッセージ キューから値を再度読み取り、再度削除または更新することができます。
- 削除または更新が成功した場合は、操作の繰り返しを避けるために、これらの値をメッセージ キューから削除します。この時点で、データベースとキャッシュされたデータの一貫性が確保されていることも確認できます。そうでない場合は、再試行する必要があります。
- 一定回数以上リトライしても成功しない場合は、ビジネス層にエラーメッセージを送信し、運用保守担当者に通知する必要があります。
- データベースデータを更新する
- データベースは操作情報を binlog ログに書き込みます。
- サブスクリプション プログラムは必要なデータとキーを抽出します
- この情報を取得するには、ビジネス以外のコードの新しいセクションを開始してください
- キャッシュ操作を削除しようとしましたが、削除に失敗したことがわかりました。
- この情報をメッセージキューに送信します
- メッセージキューから変更されたデータを再取得し、操作を再試行します。
MQを導入する理由
- アプリケーションがデータベースのデータを更新した後、更新操作がメッセージ キューに送信され、メッセージ キューによってキャッシュされたデータの削除が非同期的にトリガーされます。
- この利点は、データベースの更新後に例外やネットワーク遅延が発生した場合でも、データ更新操作はメッセージ キューに配置され、キャッシュされたデータとデータベース データの間で不整合が発生しないことです。