インスタンスに格納されている共有リソースに同時にアクセスして更新するために多数の要求が送信される場合、正確で効率的な同時実行制御メカニズムが必要です。 このメカニズムは、ロジックの例外とデータエラーの防止に役立つ必要があります。 メカニズムの1つは楽観的なロックです。 オープンソースのRedisと比較して、Tair (Enterprise Edition) のTairStringデータ構造では、楽観的なロックを実装して、より高いパフォーマンスを低コストで実現できます。
並行性と最後の作家の勝利
次の図は、同時リクエストが競合状態を引き起こす一般的なシナリオを示しています。
初期段階では、key_1の値は
hello
である。 キーはstring型です。t1時点で、アプリケーション1はkey_1値
hello
を読み取る。t2時点で、アプリケーション2は、key_1値
hello
を読み取る。t3時点で、アプリケーション1は、key_1の値を
world
に変更する。t4時点で、アプリケーション2は、key_1の値を
universe
に変更する。
key_1の値は、最後の書き込み動作によって決定される。 t4時点では、アプリケーション1はkey_1の値をworldと見なすが、実際の値はuniverseである。 これは、その後の操作において潜在的な問題につながる。 このプロセスは、ラストライターウィンとして知られています。 last-writer-winsによって引き起こされる問題を解決するには、文字列データへのアクセスと更新の操作がアトミックであることを確認してください。 つまり、共有リソースとして機能する文字列データを原子変数に変換する必要があります。 これを行うには、exStringデータ構造を使用して、高性能の楽観的ロックを実装します。
TairStringを使用して楽観的なロックを実装する
TairStringは、拡張文字列 (exString) とも呼ばれ、バージョン番号を保持する文字列データ構造です。 ネイティブRedis文字列は、キーと値のみで構成されます。 TairStringsは、キー、値、およびバージョン番号で構成されます。 このため、TairStringは楽観的なロックに最適です。 TairStringコマンドの詳細については、「exString」をご参照ください。
TairStringsは、ネイティブのRedis文字列とは異なります。 TairStringsとネイティブRedis文字列でサポートされているコマンドは互換性がありません。
TairStringには次の機能があります。
バージョン番号は、各キーに対して提供される。 バージョン番号は、キーの現在のバージョンを示します。 EXSETコマンドを実行してキーを作成する場合、キーのデフォルトのバージョン番号は1です。
EXGETコマンドを実行してキーを照会すると、valueとversionの2つのフィールドの値を取得できます。
TairString値を更新すると、バージョンが検証されます。 検証が失敗すると、次のエラーメッセージが返されます。
ERR update version is stale
。 検証が成功すると、TairString値が更新され、バージョン番号が自動的に1だけインクリメントされます。TairStringは、ビット操作を除くRedis Stringのすべての機能を統合します。
これらの機能により、ロックメカニズムはTairStringデータにネイティブです。 TairStringは楽観的なロックを実装することが容易になります。 例:
while(true){
{value, version} = EXGET(key); // Retrieve the value and version number of the key.
value2 = update(...); // Save the new value as value 2.
ret = EXSET(key, value2, version); // Update the key and assign the return value to the ret variable.
if(ret == OK)
break; // If the return value is OK, the update is successful and the while loop exits.
else if (ret.contanis("version is stale"))
continue; // If the return value contains the "version is stale" error message, the update fails and the while loop is repeated.
}
TairStringを削除し、削除したTairStringと同じキーを持つTairStringを作成した場合、新しいTairStringのキーバージョンは1になります。 新しいTairStringは、削除されたTairStringのキーバージョンを継承しません。
ABSオプションを指定すると、バージョン検証をスキップし、現在のバージョンを強制的に上書きしてTairStringを更新できます。 詳細については、「EXSET」をご参照ください。
楽観的なロックのためのリソース消費を減らす
上記のサンプルコードでは、EXGETコマンドを実行した後に別のクライアントが共有リソースを更新すると、更新失敗メッセージが表示され、whileループが繰り返されます。 更新が成功するまで、EXGETコマンドを繰り返し実行して、共有リソースの値とバージョン番号を取得します。 その結果、各whileループでTairにアクセスするために2つのI/O操作が実行されます。 ただし、TairStringのEXCASコマンドを使用して、whileループごとに1つのアクセス要求を送信するだけで済みます。 これにより、システムリソースの消費が大幅に減少し、並行性の高いシナリオでのサービスパフォーマンスが向上します。
EXCASコマンドを実行すると、コマンドでバージョン番号を指定してバージョンを確認できます。 検証が成功すると、TairString値が更新されます。 検証が失敗した場合、次の要素が返されます。
「ERRアップデートバージョンは古くなっています」
Value
Version
更新が失敗した場合、コマンドはTairStringの現在のバージョン番号を返します。 現在のバージョン番号を取得するために別のクエリを実行する必要はなく、whileループごとに必要なアクセス要求は1つだけです。 サンプルコード:
while(true){
{ret, value, version} = excas(key, new_value, old_version) // Use the CAS command to replace the original value with a new value.
if(ret == OK)
break; // If the return value is OK, the update is successful and the while loop exits.
else (if ret.contanis("update version is stale")) // If the return value contains the "update version is stale" error message, the update fails. The values of the value and old_version variables are updated.
update(value);
old_version = version;
}