雲の隙間から太陽を見る: Redis とデータベース間の一貫性を確保するにはどうすればよいですか?

概要

Redis の使用中に、キャッシュの侵入、キャッシュの破壊、キャッシュなだれ、キャッシュとデータベース (MySQL) 間の二重書き込みの整合性という 4 つの異常な問題が発生します。

最初の 3 つの問題はビジネスの規模によって異なりますが、最後の問題は避けられません。電子商取引ビジネスが小規模であっても、キャッシュとデータベース内の価格や在庫情報の不一致は、企業に多大な損失をもたらす可能性があります。したがって、この質問は面接でも必須の質問となっています。

では、Redis と MySQL の二重書き込みデータ間の不整合の問題をどのように解決すればよいでしょうか? あるいは、それらの間の一貫性をどのように確保しますか?

問題の背景

質問に答えるには、まずその質問がなぜ生じたのかを理解する必要があります。

Redis テクノロジーが導入されていない場合、単にデータ (単一インスタンス) を MySQl に書き込むときに整合性の問題が発生しますか? もちろん違います。

MySQL の InnoDB エンジンのトランザクション機能はこれをすでに保証しているため、詳細については説明しません。

Redis導入後の読み込み処理は、キャッシュが存在する場合は直接読み込み、キャッシュが存在しない場合はデータベースのデータを直接読み込み、キャッシュに更新するという流れになるためです。したがって、次のような一貫したシナリオが生じます。

  1. キャッシュには MySQL のデータと一致するデータがあります。

  2. キャッシュにはデータがありません。MySQL は最新のデータを保存します。

最初のケースでは、MySQL と Redis データが同じであることを確認する必要があります。2 番目のケースでは、データが適切な方法でキャッシュに更新されていることを確認する必要があります。上記 2 点を保証するために、MySQL と Redis 間のトランザクションには強い制約が設けられておらず、何らかの状況が崩れるとデータの不整合が発生します。

キャッシュの使用方法は、読み取り/書き込みキャッシュと読み取りキャッシュに分けられます。

  • 読み取りおよび書き込みキャッシュ: シナリオの読み取りに加えて、キャッシュ内のデータは直接変更されます。これは、同期直接書き込みと非同期ライトバックに分けられます。同期ライトスルーは、キャッシュされたデータとデータベースを同期的に直接変更します。非同期ライトバックは、キャッシュが削除されたときにデータベースに書き戻します。このスキームは不一致の可能性が高く、多くの読み取りおよび書き込みシナリオで使用されるため、ここでは考慮しません。

  • キャッシュの読み取り: キャッシュの内容を直接変更しません。データベースが変更されると、キャッシュは直接無効化され、キャッシュに存在しない場合は、データベースの値が読み取られてキャッシュに設定されます。この種のシナリオは、読み取りを増やし、書き込みを減らすのに適しており、主にこの解決策について説明します。

最初に結論を述べます: 実際、技術的に言えば、Redis とデータベース間の厳密な整合性を保証することはほとんどできません。すべての解決策は、不整合の可能性と不整合の時間を可能な限り減らすことです。

1. まずキャッシュを削除してからデータベースを更新します

最初にキャッシュを削除してからデータベースを更新すると、次の状況が発生する可能性があります。

  1. スレッド 1 はキャッシュを削除し、データベースを更新する準備をします。

  2. スレッド 2 はデータの読み取りを開始しますが、キャッシュがないことが判明したため、データベース内のデータを読み取ります。読み取りは書き込みよりもはるかに高速であるため、この時点ではスレッド 1 が更新を完了していない可能性が高くなります。

  3. スレッド 2 は古いデータを Redis に更新し、スレッド 1 はこの時点でデータベースの変更を完了します。データの不整合が発生します。

先に削除してしまうとこのような問題が発生する可能性があるので、後で再度削除すれば大丈夫です。

 

2. キャッシュ遅延二重削除

前の解決策と比較して、削除を 1 つ追加しましたが、これには欠点もあります。

睡眠時間はどうやって決めるの?スリープ時間は、スレッド 2 がキャッシュを読み取って設定する時間よりも長く、小さすぎると設定後に削除できない可能性があり、大きすぎるとシステムのスループットに影響します。

3. まずデータベースを更新してからキャッシュを削除します

 

一度目は削除できず、二回目だけ削除することはできますか?まずデータベースを更新してから、キャッシュを削除します。このようにして、スレッド 2 はスレッド 1 がデータを送信するまで古いデータを読み取ることができ、スレッド 1 が更新された後でのみデータベースにアクセスして新しい値を読み取ります。この問題は完全に解決されているようです。

しかし、特殊なケースがあります。

  1. スレッド 1 はデータベースを更新しますが、トランザクションはまだコミットしていません。次に、キャッシュを削除し、トランザクションがコミットされるまで待ってから、データの変更を完了します。

  2. しかし、トランザクションの最終コミットとキャッシュの削除の間のギャップで、2 番目のスレッドがキャッシュを読み取ってキャッシュが存在しないことがわかり、データベースの古い値を設定するプロセスが発生します。

  3. このようにして、スレッド 1 が最終的にデータを送信した後、データベースとキャッシュの不整合の問題が再び発生します。

しかし、その可能性はどのくらいでしょうか? とてもとても小さい。コミット操作間のギャップは明らかに非常に短く、必要なネットワーク リンクは 1 つだけです。しかし、可能性はまだ存在します。

この問題は、Lua スクリプトのアトミックな操作として、ロックを設定し、キャッシュを削除するときに短い有効期限を追加するという方法でも解決できます。キャッシュが存在しないことがスレッドで検出された場合、スレッドはブロックされて待機するか、クエリ失敗を返します。データベース トランザクションが正常にコミットされた後、ロックを削除します。もちろん、この解決策ではスループットが低下するか、インターフェイスが一時的に使用できなくなります。

スレッド 1 のトランザクション送信のタイミングがキャッシュが削除される前に送信された場合はどうなるでしょうか?

 

このプロセスははるかに簡単に見えます。スレッド 1 のデータベースが変更された後、スレッド 2 は Redis を削除する前に短期間に古いデータを読み取りますが、最終的にはスレッド 1 によって削除されます。このスキームは明らかに、一貫性のない時間とコストが最も低くなります。

しかし、キャッシュを削除する最後のステップが失敗した場合はどうなるでしょうか? 古いデータは常にキャッシュ内にあります。

 

4. メッセージキューのリトライ削除

2 番目の解決策でも 3 番目の解決策でも、キャッシュの削除に失敗する可能性があるため、最終的な整合性を確保するために、最後にキャッシュを削除するときにメッセージ キューを使用して再試行メカニズムを増やすことができます。

  1. データベースを更新します。

  2. キャッシュを削除し、MQ メッセージを送信します。

  3. MQ が受信され、削除は成功しました。それ以外の場合は再試行を続けます。

この解決策は、ビジネスへの侵入の問題を引き起こす可能性があります。しかし、大丈夫だと思います。削除ロジックをカプセル化し、削除を記述して MQ を一緒に送信し、MQ を一般的な削除関数として使用できます。

おすすめ

転載: blog.csdn.net/m0_37723088/article/details/131221814