Redis:四、事務性
redisのトランザクションのサポートは現在も簡単です.redisは、1つのclientが開始したトランザクション内のコマンドが連続的に実行され、その間に他のclientのコマンドが挿入されないことを保証します.redisはすべてのclientのリクエストを処理するために単一のスレッドであるため、これは容易である.一般的にredisは、クライアントから送信されたコマンドを受信するとすぐに処理され、処理結果が返されますが、クライアントが接続中にmultiコマンドを発行すると、後続のコマンドがすぐに実行されるのではなく、キューに先に配置されるトランザクションコンテキストに入ります.この接続からexecコマンドを受けると、redisはキュー内のすべてのコマンドを順番に実行します.すべてのコマンドの実行結果をパッケージしてclientに返します.その後、この接続はトランザクションコンテキストを終了します.例を見てみましょう
この例から、incr a、incr bコマンドが発行された後、実行されずにキューに格納されていることがわかります.execを呼び出すと2つのコマンドが連続的に実行され、最後に2つのコマンドが実行された結果、discardコマンドを呼び出してトランザクションをキャンセルすることができます.次に上の例
redisトランザクションは本質的にシーケンス化された独立性レベルに相当するとはいえます.ただし、トランザクションコンテキストのコマンドはキューに並ぶだけですぐに実行されないため、トランザクション内の書き込み操作はトランザクション内の読み取り操作の結果に依存することはできません.次の例を見てください
すなわち、redisは、aへのアクセスを同期させるためのロックメカニズムを提供していない.
redis 2.1の後にwatchコマンドが追加され、楽観的なロックを実現することができます.incrコマンドを正しく実装する例を見て、前にwatch aを付けただけです.
redisのトランザクションの実装はこのように簡単で、もちろんいくつかの問題があります.最初の問題はredisがトランザクションの各コマンドを連続的に実行することしか保証できないことですが、トランザクションの1つのコマンドが失敗した場合、使用するコマンドタイプが一致しないなど、他のコマンドはロールバックされません.
最後の非常に珍しい問題は、トランザクションの実行中にredisが意外に停止した場合です.残念ながら一部の命令だけが実行され、後の命令も破棄されました.もちろん、append-only file方式を永続化すると、redisはトランザクション全体に単一のwrite操作で書き込まれます.つまり、トランザクションがディスクに一部しか書き込まれていない可能性があります.一部の書き込みトランザクションが発生した場合、redisが再起動すると検出され、終了に失敗します.redis-check-aofツールを使用して修復できます.一部書き込まれたトランザクション内容が削除されます.修復が完了すると再起動できます.
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> exec
1. (integer) 1
2. (integer) 1
この例から、incr a、incr bコマンドが発行された後、実行されずにキューに格納されていることがわかります.execを呼び出すと2つのコマンドが連続的に実行され、最後に2つのコマンドが実行された結果、discardコマンドを呼び出してトランザクションをキャンセルすることができます.次に上の例
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> discard
OK
redis> get a
"1"
redis> get b
"1"
は、今回のincr a incr bが実行されていないことを発見することができる.discardコマンドは、トランザクションのコマンドキューを空にし、トランザクションコンテキストを終了します.redisトランザクションは本質的にシーケンス化された独立性レベルに相当するとはいえます.ただし、トランザクションコンテキストのコマンドはキューに並ぶだけですぐに実行されないため、トランザクション内の書き込み操作はトランザクション内の読み取り操作の結果に依存することはできません.次の例を見てください
redis> multi
OK
redis> get a
QUEUED
redis> get b
QUEUED
redis> exec
1. "1"
2. "1"
問題を発見したでしょう.トランザクションでincr操作を実現したい場合はどうすればいいですか?そうしてもいいですか.redis> get a
"1"
redis> multi
OK
redis> set a 2
QUEUED
redis> exec
1. OK
redis> get a,
"2"
結論は明らかにこれではだめだ.これはget aと直接set aと変わらない.明らかにget aとset aは、2つのコマンドが連続的に実行されることを保証することはできません(get操作はトランザクションコンテキストにありません).この操作を同時に行うクライアントが2つある可能性が高い.結果として、aを2回加えて元の1から3になることを期待しています.しかし、2つのclientのget aは、いずれも1であり、最終的に2回加算される可能性が高いが、結果は2である.主な問題は共有リソースaへのアクセスを同期していないことです.すなわち、redisは、aへのアクセスを同期させるためのロックメカニズムを提供していない.
redis 2.1の後にwatchコマンドが追加され、楽観的なロックを実現することができます.incrコマンドを正しく実装する例を見て、前にwatch aを付けただけです.
redis> watch a
OK
redis> get a
"1"
redis> multi
OK
redis> set a 2
QUEUED
redis> exec
1. OK
redis> get a,
"2"
watchコマンドは、所与のkeyを監視し、exec時に監視したkeyがwatch呼び出しから変化した場合、トランザクション全体が失敗します.watchを呼び出す複数のkeyを複数回監視することもできる.これで指定したkeyに楽観的なロックをかけることができます.注意watchのkeyは接続全体に有効であり、トランザクションも同様です.接続が切断されると、監視とトランザクションが自動的に消去されます.もちろんexec、discard、unwatchコマンドは接続中のすべての監視をクリアします.redisのトランザクションの実装はこのように簡単で、もちろんいくつかの問題があります.最初の問題はredisがトランザクションの各コマンドを連続的に実行することしか保証できないことですが、トランザクションの1つのコマンドが失敗した場合、使用するコマンドタイプが一致しないなど、他のコマンドはロールバックされません.
redis> set a 5
OK
redis> lpush b 5
(integer) 1
redis> set c 5
OK
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> incr c
QUEUED
redis> exec
1. (integer) 6
2. (error) ERR Operation against a key holding the wrong kind of value
3. (integer) 6
にはincr bが失敗したにもかかわらず、他の2つのコマンドが実行されていることがわかります.最後の非常に珍しい問題は、トランザクションの実行中にredisが意外に停止した場合です.残念ながら一部の命令だけが実行され、後の命令も破棄されました.もちろん、append-only file方式を永続化すると、redisはトランザクション全体に単一のwrite操作で書き込まれます.つまり、トランザクションがディスクに一部しか書き込まれていない可能性があります.一部の書き込みトランザクションが発生した場合、redisが再起動すると検出され、終了に失敗します.redis-check-aofツールを使用して修復できます.一部書き込まれたトランザクション内容が削除されます.修復が完了すると再起動できます.