redisトランザクション実装原理

9677 ワード

一:紹介
Redisトランザクションは通常、MULTI、EXEC、WATCHなどのコマンドを使用して完了します.redisがトランザクションを実装するメカニズムは、一般的なリレーショナル・データベースとは大きく異なります.例えば、redisのトランザクションはロールバックをサポートせず、トランザクションが実行されると他のクライアントのリクエストの実行をブロックします.
二:事務実現の詳細
redisトランザクションは、通常、最初から最後まで3つのフェーズを通過します.
1.取引開始2.入隊を命じるトランザクション実行は次の例から見ます
redis > MULTI 
OK

redis > SET "username" "bugall"
QUEUED

redis > SET "password" 161616
QUEUED

redis > GET "username"

redis > EXEC
1) ok
2) "bugall"
3) "bugall"
redis > MULTI 

トランザクションの開始をマークするMULTIコマンドは、このコマンドを実行するクライアントを非トランザクション状態からトランザクション状態に切り替えることができます.この切り替えは、クライアント状態のflagsプロパティでREDIS_を開くことによって行われます.MULTI識別が完了しました.redisの対応する部分のソースコード実装を見てみましょう.
void multiCommand(client *c) {
    if (c->flags & CLIENT_MULTI) {
        addReplyError(c,"MULTI calls can not be nested");
        return;
    }
    c->flags |= CLIENT_MULTI;   //      
    addReply(c,shared.ok);
}

トランザクションIDを開いたクライアントでは、これらのコマンドは、ユーザーの入力によってすぐに実行されないコマンドキューに一時保存されます.
redis > SET "username" "bugall"
redis > SET "password" 161616
redis > GET "username"

トランザクション・キュー内のコマンドを実行します.
redis > EXEC

ここで注意しなければならないのは、クライアントがトランザクションIDを開いた後、コマンド:EXEC、DISCARD、WATCH、MULTIコマンドだけがすぐに実行され、他のコマンドサーバはすぐに実行されず、これらのコマンドをトランザクションキューに入れ、クライアントにQUEUEDを返してredisクライアントに自分のトランザクション状態があることを返信することです.この状態はクライアント状態mstate属性に保存され、mstateの構造体タイプはmultiStateであり、multiStateの定義を参照してください.
typedef struct multiState {
    multiCmd *commands;     //  MULTI commands   
    int count;              //    
} multiState;

構造体タイプmultiCmdの構造を見てみましょう
typedef struct multiCmd {
    robj **argv;    //  
    int argc;   //    
    struct redisCommand *cmd; //    
} multiCmd;

トランザクションキューは、先頭の保存方法で、先頭のコマンドは配列の前に配置され、下位のコマンドは配列の後ろに配置されます.
三:取引の実行
トランザクションIDを開いたクライアントがEXECコマンドを送信すると、サーバが実行します.クライアント対応のトランザクションキューのコマンドです.EXECの実装の詳細を見てみましょう.
void execCommand(client *c) {
    int j;
    robj **orig_argv;
    int orig_argc;
    struct redisCommand *orig_cmd;
    int must_propagate = 0; //     ,      

    //             
    if (!(c->flags & CLIENT_MULTI)) {
        addReplyError(c,"EXEC without MULTI");
        return;
    }
    //        EXEC
    //     watch key         
    if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
        addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
                                                  shared.nullmultibulk);
        discardTransaction(c);
        goto handle_monitor;
    }

    //          
    unwatchAllKeys(c); //  redis         ,   watch key         clear    watch
    orig_argv = c->argv;
    orig_argc = c->argc;
    orig_cmd = c->cmd;
    addReplyMultiBulkLen(c,c->mstate.count);
    for (j = 0; j < c->mstate.count; j++) {
        c->argc = c->mstate.commands[j].argc;
        c->argv = c->mstate.commands[j].argv;
        c->cmd = c->mstate.commands[j].cmd;

        //      ,    
        if (!must_propagate && !(c->cmd->flags & CMD_READONLY)) {
            execCommandPropagateMulti(c);
            must_propagate = 1;
        }
        //    
        call(c,CMD_CALL_FULL);
        c->mstate.commands[j].argc = c->argc;
        c->mstate.commands[j].argv = c->argv;
        c->mstate.commands[j].cmd = c->cmd;
    }
    c->argv = orig_argv;
    c->argc = orig_argc;
    c->cmd = orig_cmd;
    //          
    discardTransaction(c);
    if (must_propagate) server.dirty++;

handle_monitor:
    if (listLength(server.monitors) && !server.loading)
        replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
}

四:watch/unwatch/discard
watch:コマンドは楽観的なロックです.EXECコマンドが実行される前に、任意の数のデータベース・キーを監視し、EXECコマンドを実行するときに、少なくとも1つのwatchのキー値が変更されているかどうかを判断します.変更されている場合はトランザクションの実行を放棄し、変更されていない場合はwatchの情報をクリアし、トランザクション・リストのコマンドを実行します.unwatch:名前の通り、watchとは逆の機能であることがわかります.1つのキー値の「傍受」を取り消す機能です.discard:クライアントのトランザクションキュー内のすべてのコマンドをクリアし、クライアントのトランザクションタグをキャンセルします.クライアントがトランザクションを実行するときにwatchにいくつかのキーを待っている場合、discardはすべてのキーのwatchをキャンセルします.
五:redis取引のACID特性
           ,   ACID                 。

トランザクションは、redisにおいて常にアトミック性(Atomicity)、コンシステント性(Consistency)、アイソシエーション(Isolation)を有し、ある特定の永続化モードで実行する場合にも耐久性(Durability)を有する.
①原子間トランザクションに原子間性があるとは、データベースがトランザクション内の複数のオペレーションを1つの全体として実行し、サーバがトランザクション内のすべてのオペレーションを実行するか、1つのオペレーションも実行しないことを意味します.しかし、redisのトランザクション機能では、トランザクションキュー内のコマンドがすべて実行されるか、1つも実行されないため、redisのトランザクションは原子的です.通常、redisトランザクションの原子性に関する2つの説を知っています.1つは、トランザクションが実行されるか、実行されないかです.もう1つの言い方は、redisトランザクションは、トランザクション内のコマンドの実行に失敗した後もコマンドが実行され、エラー前のコマンドはロールバックされません.実はこの2つの言い方はすべて正しいです.しかし、一つ欠けてはいけない.次に具体的に分析します
                 
```
redis > MULTI
OK

redis > SET username "bugall"
QUEUED

redis > EXEC
1) OK
2) "bugall"
```
    ,                。                        ,               ,  
        ,redis                ,     EXEC          。         "GET"  ,   
               ,         EXEC  ,               , EXEC               
  ,              ,            ,      。
```
redis > MULTI
OK

redis > GET
(error) ERR wrong number of arguments for 'get' command

redis > GET username
QUEUED

redis > EXEC
(error) EXECABORT Transaction discarded because of previous errors
```
redis                      ,redis          ,                     ,      
      ,                    ,        
```
redis > SET username "bugall"
OK

redis > MULTI
OK

redis > SADD member "bugall" "litengfe" "yangyifang"
QUEUED

redis > RPUSH username "b" "l" "y" //    username       
QUEUED

redis > SADD password "123456" "123456" "123456"
QUEUED

redis > EXEC
1) (integer) 3
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 3
```
redis               ,                  redis               ,     ,redis      
             ,                ,               ,          redis        。  
     redis       ,              。

②コンシステンシトランザクションにコンシステンシがあるとは、データベースがトランザクションを実行する前にコンシステンシである場合、トランザクションが実行された後も、トランザクションが正常に実行されたかどうかにかかわらず、データベースがコンシステンシであることを意味します.」コンシステンシとは、データベース自体の定義と要件に合致し、不正または無効なエラー・データが含まれていないことを意味します.redisは、慎重なエラー検出と簡単な設計によってトランザクションの一貫性を保証します.
③独立性トランザクションの独立性とは、データベース内で複数のトランザクションが同時に実行されても、各トランザクション間で相互に影響を及ぼさず、同時に実行されたトランザクションとシリアル実行されたトランザクションの結果がまったく同じであることを意味します.Redisは、トランザクション(およびトランザクションキュー内のコマンド)を実行するために単一スレッドを使用し、サーバは、トランザクションの実行中に物事を中断しないことを保証するため、redisのトランザクションは常にシリアルで実行され、トランザクションも常に分離性を有する④持続性トランザクションの耐久性は、トランザクションの実行が完了すると、このトランザクションを実行した結果は、永続的なストレージメディアに保持されます.Redisトランザクションは単純にキューでラップされたredisコマンドのセットにすぎないため、redisはトランザクションに追加の永続化機能を提供していないため、redisトランザクションの耐久性はredisが使用するモードによって決定されます.サーバが永続化されていないメモリモードで実行されると、トランザクションは耐久性を持ちません.サーバが停止すると、トランザクションデータを含むすべてのサーバデータが失われる-サーバがRDB永続化モードで動作する場合、サーバは特定の保存条件が満たされた場合にのみBGSAVEコマンドを実行し、データベースを保存操作し、非同期で実行されるBGSAVEはトランザクションデータが最初にハードディスクに保存されることを保証できない.したがって、RDB永続化モードでのトランザクションにも耐久性はありません.AOF永続化モードでサーバが実行され、appedfsyncのオプションの値がalwaysの場合、プログラムはコマンドの実行後に同期関数を呼び出し、コマンドデータをハードディスクに保存します.この構成でのトランザクションには耐久性があります.-サーバがAOF永続化モードで動作し、appedfsyncのオプションの値がeverysecの場合、プログラムは1秒に1回コマンドデータをディスクに同期します.ダウンタイムは同期待ちの1秒以内に発生する可能性があります.これにより、トランザクションデータが失われる可能性があります.この構成のトランザクションは耐久性がありません.
転入元:bugallのsf