Java 基本チュートリアル カラムにより、キャッシュとデータベース間の二重書き込みの一貫性が保証されます
頭を上げてください、プリンセス、そうしないと王冠が落ちてしまいます。
分散キャッシュは多くの分散アプリケーションに不可欠なコンポーネントです。ただし、分散キャッシュを使用すると、ストレージとキャッシュとデータベースの二重書き込みと二重書き込みが必要になる場合があります。必要なのは二重書き込みだけです。データの一貫性の問題は間違いなくあります。では、一貫性の問題をどのように解決しますか?
キャッシュ アサイド パターン
最も古典的なキャッシュ データベースの読み取りおよび書き込みパターンは、キャッシュ アサイド パターンです。
読み込み時はまずキャッシュを読み込み、キャッシュがない場合はデータベースを読み込み、データを取り出してキャッシュに入れ、同時にレスポンスを返します。
更新する場合は、データベースを更新してからキャッシュを削除してください。
なぜキャッシュを更新せずにキャッシュを削除するのでしょうか?
理由は非常に単純で、多くの場合、複雑なキャッシュ シナリオでは、キャッシュはデータベースから直接取得された値だけではありません。
たとえば、特定のテーブルのフィールドが更新された場合、対応するキャッシュは他の 2 つのテーブルのデータをクエリし、キャッシュの最新の値を計算する操作を実行する必要があります。
さらに、キャッシュの更新コストが非常に高くなる場合があります。データベースが変更されるたびに、対応するキャッシュを更新する必要があるという意味ですか?これは一部のシナリオに当てはまる場合がありますが、より複雑なキャッシュ データ計算シナリオの場合は当てはまりません。キャッシュに含まれる複数のテーブルを頻繁に変更すると、キャッシュも頻繁に更新されます。しかし問題は、このキャッシュが頻繁にアクセスされるかどうかです。
たとえば、キャッシュに含まれるテーブルのフィールドが 1 分間に 20 回または 100 回変更された場合、キャッシュは 20 回または 100 回更新されますが、このキャッシュは 20 回のみ更新されます。 1 分間に 1 回または 100 回読み取られ、大量のコールド データが含まれています。実際、キャッシュを削除するだけであれば、キャッシュの再計算は 1 分に 1 回のみとなり、オーバーヘッドが大幅に削減され、キャッシュはキャッシュの計算にのみ使用されます。
実際、キャッシュを更新する代わりにキャッシュを削除するのは、遅延計算の考え方です。使用されるかどうかに関係なく、複雑な計算を毎回やり直すのではなく、必要なときに使用するようにしてください。再計算してください。 mybatis や hibernate と同様、それらはすべて遅延読み込みという考えを持っています。部門にクエリを実行すると、その部門は従業員のリストを取得しますが、部門にクエリを実行するたびに、部門内の 1,000 人の従業員のデータも同時に検索されることは言うまでもありません。 80% の場合、この部門をチェックするには、この部門の情報にアクセスするだけで済みます。まず部門を確認し、同時に内部の従業員にアクセスする必要がある場合、内部の従業員にアクセスしたい場合にのみ、データベースに 1,000 人の従業員をクエリします。
最も基本的なキャッシュの不整合の問題と解決策
問題: まずデータベースを変更してから、キャッシュを削除します。キャッシュの削除に失敗すると、データベースには新しいデータが残り、キャッシュには古いデータが残り、データの不整合が発生します。
解決策: まずキャッシュを削除してから、データベースを変更します。データベースの変更が失敗した場合、データベースには古いデータがあり、キャッシュは空であるため、データは不整合になりません。読み取り時にはキャッシュがないため、データベース内の古いデータが読み取られてキャッシュに更新されます。
より複雑なデータの不整合の問題の分析
データが変更され、最初にキャッシュが削除され、その後データベースを変更する必要がありますが、まだ変更されていませんまだ修正されてます。リクエストが来ると、キャッシュを読み込んでキャッシュが空であることがわかり、データベースにクエリを実行して変更前の古いデータを見つけてキャッシュに入れます。その後、データ変更プログラムはデータベースの変更を完了します。
これで終わりです。データベースとキャッシュのデータが異なります。 。 。
数億のトラフィックを伴う高同時実行シナリオでキャッシュにこの問題が発生するのはなぜですか?
この問題は、データの一部の読み取りと書き込みが同時に行われる場合にのみ発生する可能性があります。実際、同時実行性が非常に低い場合、特に読み取り同時実行性が非常に低く、アクセス数が 1 日あたりわずか 10,000 件の場合、まれに、上で説明した一貫性のないシナリオが発生します。しかし、問題は、毎日数億のトラフィックがあり、毎秒数万の同時読み取りがある場合、毎秒データ更新リクエストがある限り、前述のデータベース キャッシュの不整合が発生する可能性があることです。
解決策は次のとおりです:
データを更新するときは、データの一意の識別子に基づいて操作をルーティングし、jvm 内部キューに送信します。データの読み取り時に、データがキャッシュにないことが判明した場合、データは再読み取りされ、キャッシュ操作が更新され、一意の識別子に基づいてルーティングされた後、同じ JVM 内部にも送信されます列。
キューはワーカー スレッドに対応し、各ワーカー スレッドは対応する操作をシリアルに取得し、1 つずつ実行します。この場合、データ変更操作では、まずキャッシュが削除され、次にデータベースが更新されますが、更新はまだ完了していません。このとき、読み取りリクエストが来て空のキャッシュが読み取られた場合、キャッシュ更新リクエストを先にキューに送信することができますが、このときキューにバックログされ、その後キャッシュの更新を待つことができます。同期的に完了します。
ここに最適化ポイントがあります。キュー内に複数のキャッシュ更新リクエストを並べても意味がないので、フィルタリングを行うことができます。既にキャッシュ更新リクエストがキューに存在することが判明した場合は、キューにある場合は、別の更新リクエスト操作を入れる必要はありません。前の更新操作リクエストが完了するまで待つだけです。
そのキューに対応するワーカー スレッドは、前の操作のデータベースの変更を完了した後、次の操作 (キャッシュ更新操作) を実行します。このとき、最新の値がから読み取られます。データベースにアクセスし、キャッシュに書き込みます。
リクエストがまだ待機時間の範囲内にあり、継続的なポーリングで値を取得できることが判明した場合は、リクエストは直接返されます。リクエストが一定の時間を超えて待機した場合、今度は現在の値はデータベースから直接読み取られます。
高同時実行シナリオでは、このソリューションで注意する必要がある問題:
読み取りリクエストは非常にわずかに非同期なので、読み取りタイムアウトの問題に必ず注意してください。各読み取りリクエストはタイムアウト時間の範囲内で返される必要があります。
このソリューションの最大のリスク ポイントは、データが頻繁に更新される可能性があり、その結果、キュー内に大量の更新操作のバックログが発生し、読み取りリクエストで多数のタイムアウトが発生し、最終的には大量のリクエストが直接送信されます。必ず実際のシミュレートされたテストをいくつか実行して、データがどのくらいの頻度で更新されるかを確認してください。
もう 1 つの点は、キュー内に複数のデータ項目の更新操作のバックログが存在する可能性があるため、独自のビジネス条件に従ってテストする必要があります。複数のサービスをデプロイする必要がある場合があり、各サービスはデータを共有します。更新操作。メモリ キューが実際に 100 製品の在庫変更操作を圧迫し、各在庫変更操作の完了に 10 ミリ秒かかる場合、最後の製品の読み取りリクエストは、データが取得できるまで 10 * 100 = 1000 ミリ秒 = 1 秒待機する可能性があります。これにより、読み取りリクエストが長期間ブロックされることになります。
必ずいくつかのストレス テストを実施し、ビジネス システムの実際の運用に基づいてオンライン環境をシミュレートして、最も混雑する時間帯にメモリ キューがどれだけの更新操作を圧迫する可能性があるかを確認してください。最後の更新操作に対応する読み取りリクエストがハングしますか? 読み取りリクエストが 200 ミリ秒以内に返される場合、計算すると、最も忙しい時間帯でも 10 件の更新操作のバックログが存在し、最大待機時間は 200 ミリ秒になります。大丈夫。
メモリ キューにバックログが発生する可能性のある多数の更新操作がある場合は、マシンを追加して、各マシンにデプロイされたサービス インスタンスが処理するデータを少なくする必要があります。メモリキューの操作が少なくなります。
実際、これまでのプロジェクトの経験に基づくと、一般的にデータ書き込みの頻度は非常に低いため、実際には通常、キュー内の更新操作のバックログは非常に小さいはずです。高い読み取り同時実行性と読み取りキャッシュ アーキテクチャをターゲットとするこのようなプロジェクトの場合、一般的に書き込みリクエストは非常に少なく、1 秒あたりの QPS が数百に達できれば良好です。
実際の大まかな計算
1 秒あたり 500 回の書き込み操作がある場合、5 つのタイム スライスに分割すると、200 ミリ秒ごとに 100 回の書き込み操作が発生し、20 秒に分割されます。メモリ キューでは、各メモリ キューに 5 つの書き込み操作のバックログが存在する可能性があります。各書き込み操作のパフォーマンス テスト後、通常は約 20 ミリ秒で完了するため、各メモリ キュー内のデータの読み取りリクエストは長くてもしばらくの間ハングするだけで、200 ミリ秒以内に必ず返されます。
先ほどの単純な計算により、1 台のマシンでサポートされる書き込み QPS は数百でも問題ないことがわかります。書き込み QPS が 10 倍に拡張されると、マシンも拡張され、マシンは 10 倍に拡張され、各マシンには 20 のキューが追加されます。
また、上記の状況が発生したときに別のリスクがあることを確認するために、ここでストレス テストを実行する必要があります。大量の読み取りリクエストが突然発生します。サービスには数十ミリ秒の遅延がかかります。サービスがそれを処理できるかどうか、および最大の極端な状況のピークを処理するには何台のマシンが必要かによって異なります。
ただし、すべてのデータが同時に更新されるわけではないため、キャッシュが同時に期限切れになることはありません。したがって、少数のデータのキャッシュが毎回無効になる可能性があり、その後、対応する読み取りリクエストがそれらのデータは同時にやって来ますが、量はそれほど多くないはずです。
おそらくこのサービスが複数のインスタンスをデプロイする場合、データ更新操作とキャッシュ更新操作のリクエストが Nginx サーバーを経由することを確認する必要があります。同じサービス インスタンスにルーティングされます。
たとえば、同じ製品に対するすべての読み取りおよび書き込みリクエストは同じマシンにルーティングされます。特定のリクエストパラメータに従ってサービス間のハッシュルーティングを独自に行うことも、Nginxのハッシュルーティング機能などを使用することもできます。
特定の製品の読み取りおよび書き込みリクエストが特に多く、それらがすべて同じ製品の同じキューに送信される場合特定のマシンの圧力が高すぎる可能性があります。つまり、商品データの更新時にのみキャッシュがクリアされ、その後は読み書きの同時実行が発生するため、実際には業務システムに依存しますが、更新頻度があまり高くなければ、その影響は少なくなります。問題は特に大きくありませんが、一部のマシンの負荷が高くなる可能性があることは事実です。
以上がJava実装により、キャッシュとデータベース間の二重書き込みの一貫性が確保されます。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。