今日の企業は、何万もの同時ユーザーにサービスを提供する高トラフィックの ASP.NET Web アプリケーションを開発しています。 複数のクライアントが、アプリケーション サーバーが負荷分散された環境に展開されるクラスター化された環境でキャッシュ データにアクセスできます。 このような並列状態では、複数のユーザーが同じデータにアクセスして変更を試み、競合状態を引き起こすことがよくあります。
競合状態とは、XNUMX 人以上のユーザーが同じ共有データに同時にアクセスして変更しようとしたが、最終的に間違った順序で実行した場合です。 この状況は、データの整合性と一貫性を失うリスクが高くなります。 インメモリのスケーラブルなキャッシング ソリューションの出現により、 NCache 分散ロックメカニズムを提供することで、企業は大幅に強化されたデータの一貫性を実現できます。
NCache 詳細 ドキュメントのロックと制御 NCache ドキュメント
データの一貫性のための分散ロック
NCache 分散のメカニズムを提供します ロッキング .NET では、同時更新中に特定のキャッシュ アイテムをロックできます。 このような場合にデータの一貫性を維持するには、 NCache 分散ロック マネージャーとして機能し、次の XNUMX 種類のロックを提供します。
それらについては、ブログの後半で詳しく説明します。 ここでは、次のシナリオを検討して、分散ロック サービスがないとデータの整合性が損なわれることを理解してください。
30,000 人のユーザーが同じ銀行口座に同時にアクセスし、残高は 15,000 です。 5,000 人のユーザーは 20,000 を引き出し、もう 15,000 人のユーザーは 35,000 を入金します。 正しく行われた場合、最終残高は XNUMX になります。 反対に、処理されていない競合状態が発生した場合、銀行残高は上記のように XNUMX または XNUMX になります。
この競合状態がどのように発生するかを次に示します。
- 時間t1: ユーザー1は、残高=30,000の銀行口座を取得します
- 時間t2: ユーザー2は、残高=30,000の銀行口座を取得します
- 時間t3: ユーザー1は15,000を引き出し、銀行口座の残高を更新します= 15,000
- 時間t4: ユーザー2は5,000を預金し、銀行口座の残高を更新します= 35,000
どちらの場合も、スレッドの管理に対応していないコードブロックは、銀行に壊滅的な打撃を与える可能性があります。 それで、次のセクションでは、どのように見てみましょう NCache アプリケーションロジックがスレッドセーフであることを保証するためのロックメカニズムを提供します。
楽観的ロック(アイテムバージョン)
In 楽観的ロック, NCache 使用されます キャッシュ アイテムのバージョン管理. サーバー側では、すべてのキャッシュされたオブジェクトに関連付けられたバージョン番号があり、キャッシュ アイテムが更新されるたびに増分されます。 NCache 次に、最新バージョンで作業しているかどうかを確認します。 そうでない場合は、キャッシュの更新を拒否します。 この方法では、XNUMX 人のユーザーのみが更新され、他のユーザーの更新は失敗します。
両方を使用してキャッシュにアイテムを追加できます Add or インセット 方法。
- Add メソッドは、キャッシュに新しいアイテムを追加し、アイテム バージョンを初めて保存します。
- insert メソッドは、既存のアイテムの値を上書きし、そのアイテムのバージョンを更新します。
次のコード例を見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// Pre-condition: Cache is already connected // An item is added in the cache with itemVersion // Specify the key of the cacheItem string key = "Product:1001"; // Initialize the cacheItemVersion CacheItemVersion version = null; // Get the cacheItem previously added in the cache with the version CacheItem cacheItem = cache.GetCacheItem(key, ref version); // If result is not null if (cacheitem != null) { // CacheItem is retrieved successfully with the version // If result is Product type var prod = new Product(); prod = cacheItem.GetValue(); prod.UnitsInStock++; // Create a new cacheItem with updated value var updateItem = new CacheItem(prod); CacheItemVersion version = cache.Insert(key, updateItem); // If it matches, the insert will be successful, otherwise it will fail } else { // Item could not be retrieved due to outdated CacheItemVersion } |
上記の例では、XNUMX つの異なるアプリケーションが製品のデータを含む単一のキャッシュを使用しています。 CacheItem がキャッシュに追加されます。 両方のアプリケーションが現在のバージョンのアイテムをフェッチします。 バージョン。 Application1は 商品名 次に、アイテムのバージョンを更新するキャッシュにアイテムを再挿入します。 新しいバージョン。 Application2にはまだ次のアイテムがあります バージョン. application2 がアイテムの在庫数を更新し、アイテムをキャッシュに再挿入すると、アイテムの挿入は失敗します。 そのため、Application2 は更新されたバージョンをフェッチして、その cacheItem を操作する必要があります。
NCache 詳細 楽観的ロック キャッシュアイテムのバージョン管理
ただし、新しいバージョンがキャッシュにある可能性がある既存のアイテムを取得する場合は、 NCache 提供 GetIfNewer 方法。 メソッド呼び出しの引数として現在のバージョンを指定すると、キャッシュは適切な結果を返します。 特定のバージョンがキャッシュ内のバージョンよりも小さい場合、メソッドは新しいアイテムを返し、それ以外の場合は null を返します。
以下のコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Get object from cache var result = cache.GetIfNewer(key, ref version); // Check if updated item is available if (result != null) { // An item with newer version is available if (result is Product) { // Perform operations according to business logic } } else { // No new itemVersion is available } |
上記の例では、キー Product:1001 とアイテム バージョンを使用してアイテムをキャッシュに追加します。 アイテムの新しいバージョンが利用可能な場合は、 GetIfNewer メソッドは、キャッシュ アイテム バージョンを使用してアイテムをフェッチします。
楽観的ロックで、 NCache 分散キャッシュへのすべての書き込みが、各アプリケーションが保持するバージョンと一致することを保証します。 私たちを参照してください 公式 NCache ドキュメンテーション 広範なコード例については。
NCache 詳細 GetIfNewer CacheItemVersioning
悲観的ロック(排他的ロック)
データの一貫性を確保するもう XNUMX つの方法は、キャッシュされたデータの排他ロックを取得することです。 このメカニズムはと呼ばれます 悲観的ロック . ロック ハンドルを使用してアイテムをロックし、他のすべてのユーザーがそのキャッシュ アイテムに対して書き込み操作を実行できないようにします。 あ ロックハンドル ロック API によって返されたキャッシュ内のすべてのロックされたアイテムに関連付けられたハンドルです。
次の例では、LockHandle を作成し、キー Product:1001 でアイテムを 10 秒間ロックして、アイテムが 10 秒後に自動的にロック解除されるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// Pre-Requisite: Cache is already connected // Item is already added in the cache // Specify the key of the item string key = $"Product:1001"; //Create a new LockHandle LockHandle lockHandle = null; // Specify time span of 10 seconds for which the item remains locked TimeSpan lockSpan = TimeSpan.FromSeconds(10); // Lock the item for a time span of 10 seconds bool lockAcquired = cache.Lock(key, lockSpan, out lockHandle); // Verify if the item is locked successfully if (lockAcquired == true) { // Item has been successfully locked } else { // Key does not exist // Item is already locked with a different LockHandle } |
アイテムのフェッチ中にロックを正常に取得すると、このロックを保持している限り、他のアプリケーションがこのアイテムを取得または更新できないことを認識して、アプリケーションは安全に操作を実行できるようになります。 同じロック ハンドルで Insert API を呼び出して、データを更新し、ロックを解除します。 これにより、データがキャッシュに挿入され、ロックが解除されます。すべて XNUMX 回の呼び出しで行われ、キャッシュされたデータを他のすべてのアプリケーションで使用できるようになります。
タイムアウトを使用してすべてのロックを取得する必要があることを覚えておいてください。 デフォルトでは、タイムアウトが指定されていない場合、 NCache アイテムを無期限にロックします。 タイムスパン.ゼロ. ロックを解除せずにアプリケーションがクラッシュすると、アイテムが永久にロックされたままになる場合があります。 回避策として、次のことができます 強制的に解放する しかし、この方法はお勧めできません。 したがって、デッドロックやスレッドの枯渇を避けるために、最小 TimeSpan の間アイテムをロックしてください。
NCache 詳細 悲観的なロック キャッシュされたデータのブログでロックを使用する
分散ロックでのフェイルオーバーサポート
Since NCache インメモリであり、 分散キャッシュ、完全なフェイルオーバー サポートも提供するため、データの損失がなく、可用性が高くなります。 サーバーに障害が発生した場合でも、クライアント アプリケーションはシームレスに動作し続けます。 同様に、分散システムのロックも複製され、複製ノードによって維持されます。 アプリケーションの XNUMX つがロックを取得している間にいずれかのノードに障害が発生した場合、指定されたプロパティを使用して自動的に新しいノードに伝播されます。 ロックの有効期限.
まとめ
では、楽観的または悲観的のどちらのロック メカニズムが最適でしょうか? まあ、それはあなたのユースケースとあなたが達成したいものに依存します. 楽観的ロックは、特にアプリケーションが読み取り集中型の場合に、悲観的ロックよりも優れたパフォーマンス上の利点を提供します。 一方、ペシミスティック ロックは、データの整合性の観点からはより安全です。 したがって、ロック機構は慎重に選択してください。 詳細については、 ウェブサイト。 ご不明な点がございましたら、 Rescale Support そして私たちの専門家があなたを助けましょう!