久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Redis 設(shè)計(jì)與實(shí)現(xiàn)

 黃融 2014-05-05

Redis 通過 MULTI 、 DISCARD ,、 EXEC 和 WATCH 四個(gè)命令來實(shí)現(xiàn)事務(wù)功能,, 本章首先討論使用 MULTI 、 DISCARD 和 EXEC 三個(gè)命令實(shí)現(xiàn)的一般事務(wù),, 然后再來討論帶有 WATCH 的事務(wù)的實(shí)現(xiàn),。

因?yàn)槭聞?wù)的安全性也非常重要,, 所以本章最后通過常見的 ACID 性質(zhì)對(duì) Redis 事務(wù)的安全性進(jìn)行了說明。

事務(wù)

事務(wù)提供了一種“將多個(gè)命令打包,, 然后一次性,、按順序地執(zhí)行”的機(jī)制, 并且事務(wù)在執(zhí)行的期間不會(huì)主動(dòng)中斷 —— 服務(wù)器在執(zhí)行完事務(wù)中的所有命令之后,, 才會(huì)繼續(xù)處理其他客戶端的其他命令,。

以下是一個(gè)事務(wù)的例子, 它先以 MULTI 開始一個(gè)事務(wù),, 然后將多個(gè)命令入隊(duì)到事務(wù)中,, 最后由 EXEC 命令觸發(fā)事務(wù), 一并執(zhí)行事務(wù)中的所有命令:

redis> MULTI
OK

redis> SET book-name "Mastering C++ in 21 days"
QUEUED

redis> GET book-name
QUEUED

redis> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis> SMEMBERS tag
QUEUED

redis> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"

一個(gè)事務(wù)從開始到執(zhí)行會(huì)經(jīng)歷以下三個(gè)階段:

  1. 開始事務(wù),。
  2. 命令入隊(duì),。
  3. 執(zhí)行事務(wù)。

下文將分別介紹事務(wù)的這三個(gè)階段,。

開始事務(wù)

MULTI 命令的執(zhí)行標(biāo)記著事務(wù)的開始:

redis> MULTI
OK

這個(gè)命令唯一做的就是,, 將客戶端的 REDIS_MULTI 選項(xiàng)打開, 讓客戶端從非事務(wù)狀態(tài)切換到事務(wù)狀態(tài),。

digraph normal_to_transaction {

    rankdir = LR;

    node [shape = circle, style = filled];

    edge [style = bold];

    label = "客戶端狀態(tài)的切換";

    normal [label = "非事務(wù)狀態(tài)", fillcolor = "#FADCAD"];

    transaction [label = "事務(wù)狀態(tài)", fillcolor = "#A8E270"];

    normal -> transaction [label = "打開選項(xiàng)\nREDIS_MULTI"];
}

命令入隊(duì)

當(dāng)客戶端處于非事務(wù)狀態(tài)下時(shí),, 所有發(fā)送給服務(wù)器端的命令都會(huì)立即被服務(wù)器執(zhí)行:

redis> SET msg "hello moto"
OK

redis> GET msg
"hello moto"

但是, 當(dāng)客戶端進(jìn)入事務(wù)狀態(tài)之后,, 服務(wù)器在收到來自客戶端的命令時(shí),, 不會(huì)立即執(zhí)行命令, 而是將這些命令全部放進(jìn)一個(gè)事務(wù)隊(duì)列里,, 然后返回 QUEUED ,, 表示命令已入隊(duì):

redis> MULTI
OK

redis> SET msg "hello moto"
QUEUED

redis> GET msg
QUEUED

以下流程圖展示了這一行為:

digraph enqueue {

    node [shape = plaintext, style = filled];

    edge [style = bold];

    command_in [label = "服務(wù)器接到來自客戶端的命令"];

    in_transaction_or_not [label = "客戶端是否正處于事務(wù)狀態(tài)?", shape = diamond, fillcolor = "#95BBE3"];

    enqueu_command [label = "將命令放進(jìn)事務(wù)隊(duì)列里", fillcolor = "#A8E270"];

    return_enqueued [label = "向客戶端返回 QUEUED 字符串\n表示命令已入隊(duì)", fillcolor = "#A8E270"];

    exec_command [label = "執(zhí)行命令", fillcolor = "#FADCAD"];

    return_command_result [label = "向客戶端返回命令的執(zhí)行結(jié)果", fillcolor = "#FADCAD"];

    // 

    command_in -> in_transaction_or_not;

    in_transaction_or_not -> enqueu_command [label = "是"];

    in_transaction_or_not -> exec_command [label = "否"];

    exec_command -> return_command_result;

    enqueu_command -> return_enqueued;
}

事務(wù)隊(duì)列是一個(gè)數(shù)組,, 每個(gè)數(shù)組項(xiàng)是都包含三個(gè)屬性:

  1. 要執(zhí)行的命令(cmd),。
  2. 命令的參數(shù)(argv)。
  3. 參數(shù)的個(gè)數(shù)(argc),。

舉個(gè)例子,, 如果客戶端執(zhí)行以下命令:

redis> MULTI
OK

redis> SET book-name "Mastering C++ in 21 days"
QUEUED

redis> GET book-name
QUEUED

redis> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis> SMEMBERS tag
QUEUED

那么程序?qū)榭蛻舳藙?chuàng)建以下事務(wù)隊(duì)列:

數(shù)組索引cmdargvargc
0SET["book-name", "Mastering C++ in 21 days"]2
1GET["book-name"]1
2SADD["tag", "C++", "Programming", "Mastering Series"]4
3SMEMBERS["tag"]1

執(zhí)行事務(wù)

前面說到, 當(dāng)客戶端進(jìn)入事務(wù)狀態(tài)之后,, 客戶端發(fā)送的命令就會(huì)被放進(jìn)事務(wù)隊(duì)列里,。

但其實(shí)并不是所有的命令都會(huì)被放進(jìn)事務(wù)隊(duì)列, 其中的例外就是 EXEC ,、 DISCARD ,、 MULTI 和 WATCH 這四個(gè)命令 —— 當(dāng)這四個(gè)命令從客戶端發(fā)送到服務(wù)器時(shí), 它們會(huì)像客戶端處于非事務(wù)狀態(tài)一樣, 直接被服務(wù)器執(zhí)行:

digraph not_enque_command {

    node [shape = plaintext, style = filled];

    edge [style = bold];

    command_in [label = "服務(wù)器接到來自客戶端的命令"];

    in_transaction_or_not [label = "客戶端是否正處于事務(wù)狀態(tài),?", shape = diamond, fillcolor = "#95BBE3"];

    not_exec_and_discard [label = "命令是否\nEXEC ,、 DISCARD 、\nMULTI 或 WATCH ,?", shape = diamond, fillcolor = "#FFC1C1"];

    enqueu_command [label = "將命令放進(jìn)事務(wù)隊(duì)列里", fillcolor = "#A8E270"];

    return_enqueued [label = "向客戶端返回 QUEUED 字符串\n表示命令已入隊(duì)", fillcolor = "#A8E270"];

    exec_command [label = "執(zhí)行命令", fillcolor = "#FADCAD"];

    return_command_result [label = "向客戶端返回命令的執(zhí)行結(jié)果", fillcolor = "#FADCAD"];

    // 

    command_in -> in_transaction_or_not;

    in_transaction_or_not -> not_exec_and_discard [label = "是"];

    not_exec_and_discard -> enqueu_command [label = "否"];

    not_exec_and_discard -> exec_command [label = "是"];

    in_transaction_or_not -> exec_command [label = "否"];

    exec_command -> return_command_result;

    enqueu_command -> return_enqueued;
}

如果客戶端正處于事務(wù)狀態(tài),, 那么當(dāng) EXEC 命令執(zhí)行時(shí), 服務(wù)器根據(jù)客戶端所保存的事務(wù)隊(duì)列,, 以先進(jìn)先出(FIFO)的方式執(zhí)行事務(wù)隊(duì)列中的命令: 最先入隊(duì)的命令最先執(zhí)行,, 而最后入隊(duì)的命令最后執(zhí)行。

比如說,,對(duì)于以下事務(wù)隊(duì)列:

數(shù)組索引cmdargvargc
0SET["book-name", "Mastering C++ in 21 days"]2
1GET["book-name"]1
2SADD["tag", "C++", "Programming", "Mastering Series"]4
3SMEMBERS["tag"]1

程序會(huì)首先執(zhí)行 SET 命令,, 然后執(zhí)行 GET 命令, 再然后執(zhí)行 SADD 命令,, 最后執(zhí)行 SMEMBERS 命令。

執(zhí)行事務(wù)中的命令所得的結(jié)果會(huì)以 FIFO 的順序保存到一個(gè)回復(fù)隊(duì)列中,。

比如說,,對(duì)于上面給出的事務(wù)隊(duì)列,程序?qū)殛?duì)列中的命令創(chuàng)建如下回復(fù)隊(duì)列:

數(shù)組索引回復(fù)類型回復(fù)內(nèi)容
0status code replyOK
1bulk reply"Mastering C++ in 21 days"
2integer reply3
3multi-bulk reply["Mastering Series", "C++", "Programming"]

當(dāng)事務(wù)隊(duì)列里的所有命令被執(zhí)行完之后,, EXEC 命令會(huì)將回復(fù)隊(duì)列作為自己的執(zhí)行結(jié)果返回給客戶端,, 客戶端從事務(wù)狀態(tài)返回到非事務(wù)狀態(tài), 至此,, 事務(wù)執(zhí)行完畢,。

事務(wù)的整個(gè)執(zhí)行過程可以用以下偽代碼表示:

def execute_transaction():

    # 創(chuàng)建空白的回復(fù)隊(duì)列
    reply_queue = []

    # 取出事務(wù)隊(duì)列里的所有命令、參數(shù)和參數(shù)數(shù)量
    for cmd, argv, argc in client.transaction_queue:

        # 執(zhí)行命令,,并取得命令的返回值
        reply = execute_redis_command(cmd, argv, argc)

        # 將返回值追加到回復(fù)隊(duì)列末尾
        reply_queue.append(reply)

    # 清除客戶端的事務(wù)狀態(tài)
    clear_transaction_state(client)

    # 清空事務(wù)隊(duì)列
    clear_transaction_queue(client)

    # 將事務(wù)的執(zhí)行結(jié)果返回給客戶端
    send_reply_to_client(client, reply_queue)

在事務(wù)和非事務(wù)狀態(tài)下執(zhí)行命令

無論在事務(wù)狀態(tài)下,, 還是在非事務(wù)狀態(tài)下, Redis 命令都由同一個(gè)函數(shù)執(zhí)行,, 所以它們共享很多服務(wù)器的一般設(shè)置,, 比如 AOF 的配置、RDB 的配置,,以及內(nèi)存限制,,等等。

不過事務(wù)中的命令和普通命令在執(zhí)行上還是有一點(diǎn)區(qū)別的,,其中最重要的兩點(diǎn)是:

  1. 非事務(wù)狀態(tài)下的命令以單個(gè)命令為單位執(zhí)行,,前一個(gè)命令和后一個(gè)命令的客戶端不一定是同一個(gè);

    而事務(wù)狀態(tài)則是以一個(gè)事務(wù)為單位,,執(zhí)行事務(wù)隊(duì)列中的所有命令:除非當(dāng)前事務(wù)執(zhí)行完畢,,否則服務(wù)器不會(huì)中斷事務(wù),也不會(huì)執(zhí)行其他客戶端的其他命令。

  2. 在非事務(wù)狀態(tài)下,,執(zhí)行命令所得的結(jié)果會(huì)立即被返回給客戶端,;

    而事務(wù)則是將所有命令的結(jié)果集合到回復(fù)隊(duì)列,再作為 EXEC 命令的結(jié)果返回給客戶端,。

事務(wù)狀態(tài)下的 DISCARD ,、 MULTI 和 WATCH 命令

除了 EXEC 之外, 服務(wù)器在客戶端處于事務(wù)狀態(tài)時(shí),, 不加入到事務(wù)隊(duì)列而直接執(zhí)行的另外三個(gè)命令是 DISCARD ,、 MULTI 和 WATCH 。

DISCARD 命令用于取消一個(gè)事務(wù),, 它清空客戶端的整個(gè)事務(wù)隊(duì)列,, 然后將客戶端從事務(wù)狀態(tài)調(diào)整回非事務(wù)狀態(tài), 最后返回字符串 OK 給客戶端,, 說明事務(wù)已被取消,。

Redis 的事務(wù)是不可嵌套的, 當(dāng)客戶端已經(jīng)處于事務(wù)狀態(tài),, 而客戶端又再向服務(wù)器發(fā)送 MULTI 時(shí),, 服務(wù)器只是簡(jiǎn)單地向客戶端發(fā)送一個(gè)錯(cuò)誤, 然后繼續(xù)等待其他命令的入隊(duì),。 MULTI 命令的發(fā)送不會(huì)造成整個(gè)事務(wù)失敗,, 也不會(huì)修改事務(wù)隊(duì)列中已有的數(shù)據(jù)。

WATCH 只能在客戶端進(jìn)入事務(wù)狀態(tài)之前執(zhí)行,, 在事務(wù)狀態(tài)下發(fā)送 WATCH 命令會(huì)引發(fā)一個(gè)錯(cuò)誤,, 但它不會(huì)造成整個(gè)事務(wù)失敗, 也不會(huì)修改事務(wù)隊(duì)列中已有的數(shù)據(jù)(和前面處理 MULTI 的情況一樣),。

帶 WATCH 的事務(wù)

WATCH 命令用于在事務(wù)開始之前監(jiān)視任意數(shù)量的鍵: 當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí),, 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶端修改了, 那么整個(gè)事務(wù)不再執(zhí)行,, 直接返回失敗,。

以下示例展示了一個(gè)執(zhí)行失敗的事務(wù)例子:

redis> WATCH name
OK

redis> MULTI
OK

redis> SET name peter
QUEUED

redis> EXEC
(nil)

以下執(zhí)行序列展示了上面的例子是如何失敗的:

時(shí)間客戶端 A客戶端 B
T1WATCH name 
T2MULTI 
T3SET name peter 
T4 SET name john
T5EXEC 

在時(shí)間 T4 ,客戶端 B 修改了 name 鍵的值,, 當(dāng)客戶端 A 在 T5 執(zhí)行 EXEC 時(shí),,Redis 會(huì)發(fā)現(xiàn) name 這個(gè)被監(jiān)視的鍵已經(jīng)被修改, 因此客戶端 A 的事務(wù)不會(huì)被執(zhí)行,,而是直接返回失敗,。

下文就來介紹 WATCH 的實(shí)現(xiàn)機(jī)制,并且看看事務(wù)系統(tǒng)是如何檢查某個(gè)被監(jiān)視的鍵是否被修改,,從而保證事務(wù)的安全性的,。

WATCH 命令的實(shí)現(xiàn)

在每個(gè)代表數(shù)據(jù)庫的 redis.h/redisDb 結(jié)構(gòu)類型中,, 都保存了一個(gè) watched_keys 字典, 字典的鍵是這個(gè)數(shù)據(jù)庫被監(jiān)視的鍵,, 而字典的值則是一個(gè)鏈表,, 鏈表中保存了所有監(jiān)視這個(gè)鍵的客戶端。

比如說,,以下字典就展示了一個(gè) watched_keys 字典的例子:

digraph watched_keys {

    rankdir = LR;

    node [shape = record, style = filled];

    edge [style = bold];

    // keys

    watched_keys [label = "watched_keys |<key1> key1 |<key2> key2 |<key3> key3 | ... |<keyN> keyN", fillcolor = "#A8E270"];

    // clients blocking for key1
    client1 [label = "client1", fillcolor = "#95BBE3"];
    client5 [label = "client5", fillcolor = "#95BBE3"];
    client2 [label = "client2", fillcolor = "#95BBE3"];
    null_1 [label = "NULL", shape = plaintext];
    
    watched_keys:key1 -> client2;
    client2 -> client5;
    client5 -> client1;
    client1 -> null_1;

    // clients blocking for key2
    client7 [label = "client7", fillcolor = "#95BBE3"];
    null_2 [label = "NULL", shape = plaintext];

    watched_keys:key2 -> client7;
    client7 -> null_2;

    // key3

    client3 [label = "client3", fillcolor = "#95BBE3"];
    client4 [label = "client4", fillcolor = "#95BBE3"];
    client6 [label = "client6", fillcolor = "#95BBE3"];
    null_3 [label = "NULL", shape = plaintext];

    watched_keys:key3 -> client3;
    client3 -> client4;
    client4 -> client6;
    client6 -> null_3;
}

其中,, 鍵 key1 正在被 client2 、 client5 和 client1 三個(gè)客戶端監(jiān)視,, 其他一些鍵也分別被其他別的客戶端監(jiān)視著,。

WATCH 命令的作用, 就是將當(dāng)前客戶端和要監(jiān)視的鍵在 watched_keys 中進(jìn)行關(guān)聯(lián),。

舉個(gè)例子,, 如果當(dāng)前客戶端為 client10086 , 那么當(dāng)客戶端執(zhí)行 WATCH key1 key2 時(shí),, 前面展示的 watched_keys 將被修改成這個(gè)樣子:

digraph new_watched_keys {

    rankdir = LR;

    node [shape = record, style = filled];

    edge [style = bold];

    // keys

    watched_keys [label = "watched_keys |<key1> key1 |<key2> key2 |<key3> key3 | ... |<keyN> keyN", fillcolor = "#A8E270"];

    // clients blocking for key1
    client1 [label = "client1", fillcolor = "#95BBE3"];
    client5 [label = "client5", fillcolor = "#95BBE3"];
    client2 [label = "client2", fillcolor = "#95BBE3"];
    client10086 [label = "client10086", fillcolor = "#FFC1C1"];
    null_1 [label = "NULL", shape = plaintext];
    
    watched_keys:key1 -> client2;
    client2 -> client5;
    client5 -> client1;
    client1 -> client10086;
    client10086 -> null_1;

    // clients blocking for key2
    client7 [label = "client7", fillcolor = "#95BBE3"];
    client10086_2 [label = "client10086", fillcolor = "#FFC1C1"];
    null_2 [label = "NULL", shape = plaintext];

    watched_keys:key2 -> client7;
    client7 -> client10086_2;
    client10086_2 -> null_2;

    // key3

    client3 [label = "client3", fillcolor = "#95BBE3"];
    client4 [label = "client4", fillcolor = "#95BBE3"];
    client6 [label = "client6", fillcolor = "#95BBE3"];
    null_3 [label = "NULL", shape = plaintext];

    watched_keys:key3 -> client3;
    client3 -> client4;
    client4 -> client6;
    client6 -> null_3;
}

通過 watched_keys 字典,, 如果程序想檢查某個(gè)鍵是否被監(jiān)視, 那么它只要檢查字典中是否存在這個(gè)鍵即可,; 如果程序要獲取監(jiān)視某個(gè)鍵的所有客戶端,, 那么只要取出鍵的值(一個(gè)鏈表), 然后對(duì)鏈表進(jìn)行遍歷即可,。

WATCH 的觸發(fā)

在任何對(duì)數(shù)據(jù)庫鍵空間(key space)進(jìn)行修改的命令成功執(zhí)行之后 (比如 FLUSHDB ,、 SET ,、 DEL ,、 LPUSH 、 SADD ,、 ZREM ,,諸如此類), multi.c/touchWatchedKey 函數(shù)都會(huì)被調(diào)用 —— 它檢查數(shù)據(jù)庫的 watched_keys 字典,, 看是否有客戶端在監(jiān)視已經(jīng)被命令修改的鍵,, 如果有的話, 程序?qū)⑺斜O(jiān)視這個(gè)/這些被修改鍵的客戶端的 REDIS_DIRTY_CAS 選項(xiàng)打開:

digraph dirty_cas {

    rankdir = LR;

    node [shape = circle, style = filled];

    edge [style = bold];

    label = "客戶端狀態(tài)的切換";

    normal [label = "非事務(wù)狀態(tài)", fillcolor = "#FADCAD"];

    transaction [label = "事務(wù)狀態(tài)", fillcolor = "#A8E270"];

    dirty_cas [label = "事務(wù)安全性\n已被破壞", fillcolor = "#B22222"];

    normal -> transaction [label = "打開選項(xiàng)\nREDIS_MULTI"];

    transaction -> dirty_cas [label = "打開選項(xiàng)\nREDIS_DIRTY_CAS"];
}

當(dāng)客戶端發(fā)送 EXEC 命令,、觸發(fā)事務(wù)執(zhí)行時(shí),, 服務(wù)器會(huì)對(duì)客戶端的狀態(tài)進(jìn)行檢查:

  • 如果客戶端的 REDIS_DIRTY_CAS 選項(xiàng)已經(jīng)被打開,那么說明被客戶端監(jiān)視的鍵至少有一個(gè)已經(jīng)被修改了,,事務(wù)的安全性已經(jīng)被破壞,。服務(wù)器會(huì)放棄執(zhí)行這個(gè)事務(wù),直接向客戶端返回空回復(fù),,表示事務(wù)執(zhí)行失敗,。
  • 如果 REDIS_DIRTY_CAS 選項(xiàng)沒有被打開,那么說明所有監(jiān)視鍵都安全,服務(wù)器正式執(zhí)行事務(wù),。

可以用一段偽代碼來表示這個(gè)檢查:

def check_safety_before_execute_trasaction():

    if client.state & REDIS_DIRTY_CAS:
        # 安全性已破壞,,清除事務(wù)狀態(tài)
        clear_transaction_state(client)
        # 清空事務(wù)隊(duì)列
        clear_transaction_queue(client)
        # 返回空回復(fù)給客戶端
        send_empty_reply(client)
    else:
        # 安全性完好,執(zhí)行事務(wù)
        execute_transaction()

舉個(gè)例子,,假設(shè)數(shù)據(jù)庫的 watched_keys 字典如下圖所示:

digraph watched_keys {

    rankdir = LR;

    node [shape = record, style = filled];

    edge [style = bold];

    // keys

    watched_keys [label = "watched_keys |<key1> key1 |<key2> key2 |<key3> key3 | ... |<keyN> keyN", fillcolor = "#A8E270"];

    // clients blocking for key1
    client1 [label = "client1", fillcolor = "#95BBE3"];
    client5 [label = "client5", fillcolor = "#95BBE3"];
    client2 [label = "client2", fillcolor = "#95BBE3"];
    null_1 [label = "NULL", shape = plaintext];
    
    watched_keys:key1 -> client2;
    client2 -> client5;
    client5 -> client1;
    client1 -> null_1;

    // clients blocking for key2
    client7 [label = "client7", fillcolor = "#95BBE3"];
    null_2 [label = "NULL", shape = plaintext];

    watched_keys:key2 -> client7;
    client7 -> null_2;

    // key3

    client3 [label = "client3", fillcolor = "#95BBE3"];
    client4 [label = "client4", fillcolor = "#95BBE3"];
    client6 [label = "client6", fillcolor = "#95BBE3"];
    null_3 [label = "NULL", shape = plaintext];

    watched_keys:key3 -> client3;
    client3 -> client4;
    client4 -> client6;
    client6 -> null_3;
}

如果某個(gè)客戶端對(duì) key1 進(jìn)行了修改(比如執(zhí)行 DEL key1 ),, 那么所有監(jiān)視 key1 的客戶端, 包括 client2 ,、 client5 和 client1 的REDIS_DIRTY_CAS 選項(xiàng)都會(huì)被打開,, 當(dāng)客戶端 client2 、 client5 和 client1 執(zhí)行 EXEC 的時(shí)候,, 它們的事務(wù)都會(huì)以失敗告終,。

最后,當(dāng)一個(gè)客戶端結(jié)束它的事務(wù)時(shí),,無論事務(wù)是成功執(zhí)行,,還是失敗, watched_keys 字典中和這個(gè)客戶端相關(guān)的資料都會(huì)被清除,。

事務(wù)的 ACID 性質(zhì)

勘誤:Redis 的事務(wù)是保證原子性的,,本節(jié)的內(nèi)容將原子性和回滾功能混淆了,等待修復(fù)中,。 —— 2013.6.23

在傳統(tǒng)的關(guān)系式數(shù)據(jù)庫中,,常常用 ACID 性質(zhì)來檢驗(yàn)事務(wù)功能的安全性。

Redis 事務(wù)保證了其中的一致性(C)和隔離性(I),,但并不保證原子性(A)和持久性(D),。

以下四小節(jié)是關(guān)于這四個(gè)性質(zhì)的詳細(xì)討論。

原子性(Atomicity)

單個(gè) Redis 命令的執(zhí)行是原子性的,,但 Redis 沒有在事務(wù)上增加任何維持原子性的機(jī)制,,所以 Redis 事務(wù)的執(zhí)行并不是原子性的。

如果一個(gè)事務(wù)隊(duì)列中的所有命令都被成功地執(zhí)行,,那么稱這個(gè)事務(wù)執(zhí)行成功,。

另一方面,如果 Redis 服務(wù)器進(jìn)程在執(zhí)行事務(wù)的過程中被停止 —— 比如接到 KILL 信號(hào),、宿主機(jī)器停機(jī),,等等,那么事務(wù)執(zhí)行失敗,。

當(dāng)事務(wù)失敗時(shí),,Redis 也不會(huì)進(jìn)行任何的重試或者回滾動(dòng)作。

一致性(Consistency)

Redis 的一致性問題可以分為三部分來討論:入隊(duì)錯(cuò)誤,、執(zhí)行錯(cuò)誤,、Redis 進(jìn)程被終結(jié),。

入隊(duì)錯(cuò)誤

在命令入隊(duì)的過程中,如果客戶端向服務(wù)器發(fā)送了錯(cuò)誤的命令,,比如命令的參數(shù)數(shù)量不對(duì),,等等, 那么服務(wù)器將向客戶端返回一個(gè)出錯(cuò)信息,, 并且將客戶端的事務(wù)狀態(tài)設(shè)為 REDIS_DIRTY_EXEC ,。

當(dāng)客戶端執(zhí)行 EXEC 命令時(shí), Redis 會(huì)拒絕執(zhí)行狀態(tài)為 REDIS_DIRTY_EXEC 的事務(wù),, 并返回失敗信息,。

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command

redis 127.0.0.1:6379> EXISTS key
QUEUED

redis 127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

因此,帶有不正確入隊(duì)命令的事務(wù)不會(huì)被執(zhí)行,,也不會(huì)影響數(shù)據(jù)庫的一致性,。

執(zhí)行錯(cuò)誤

如果命令在事務(wù)執(zhí)行的過程中發(fā)生錯(cuò)誤,比如說,,對(duì)一個(gè)不同類型的 key 執(zhí)行了錯(cuò)誤的操作,, 那么 Redis 只會(huì)將錯(cuò)誤包含在事務(wù)的結(jié)果中, 這不會(huì)引起事務(wù)中斷或整個(gè)失敗,,不會(huì)影響已執(zhí)行事務(wù)命令的結(jié)果,,也不會(huì)影響后面要執(zhí)行的事務(wù)命令, 所以它對(duì)事務(wù)的一致性也沒有影響,。

Redis 進(jìn)程被終結(jié)

如果 Redis 服務(wù)器進(jìn)程在執(zhí)行事務(wù)的過程中被其他進(jìn)程終結(jié),,或者被管理員強(qiáng)制殺死,那么根據(jù) Redis 所使用的持久化模式,,可能有以下情況出現(xiàn):

  • 內(nèi)存模式:如果 Redis 沒有采取任何持久化機(jī)制,,那么重啟之后的數(shù)據(jù)庫總是空白的,所以數(shù)據(jù)總是一致的,。

  • RDB 模式:在執(zhí)行事務(wù)時(shí),,Redis 不會(huì)中斷事務(wù)去執(zhí)行保存 RDB 的工作,,只有在事務(wù)執(zhí)行之后,,保存 RDB 的工作才有可能開始。所以當(dāng) RDB 模式下的 Redis 服務(wù)器進(jìn)程在事務(wù)中途被殺死時(shí),,事務(wù)內(nèi)執(zhí)行的命令,,不管成功了多少,都不會(huì)被保存到 RDB 文件里,?;謴?fù)數(shù)據(jù)庫需要使用現(xiàn)有的 RDB 文件,而這個(gè) RDB 文件的數(shù)據(jù)保存的是最近一次的數(shù)據(jù)庫快照(snapshot),,所以它的數(shù)據(jù)可能不是最新的,,但只要 RDB 文件本身沒有因?yàn)槠渌麊栴}而出錯(cuò),,那么還原后的數(shù)據(jù)庫就是一致的。

  • AOF 模式:因?yàn)楸4?AOF 文件的工作在后臺(tái)線程進(jìn)行,,所以即使是在事務(wù)執(zhí)行的中途,,保存 AOF 文件的工作也可以繼續(xù)進(jìn)行,因此,,根據(jù)事務(wù)語句是否被寫入并保存到 AOF 文件,,有以下兩種情況發(fā)生:

    1)如果事務(wù)語句未寫入到 AOF 文件,或 AOF 未被 SYNC 調(diào)用保存到磁盤,,那么當(dāng)進(jìn)程被殺死之后,,Redis 可以根據(jù)最近一次成功保存到磁盤的 AOF 文件來還原數(shù)據(jù)庫,只要 AOF 文件本身沒有因?yàn)槠渌麊栴}而出錯(cuò),,那么還原后的數(shù)據(jù)庫總是一致的,,但其中的數(shù)據(jù)不一定是最新的。

    2)如果事務(wù)的部分語句被寫入到 AOF 文件,,并且 AOF 文件被成功保存,,那么不完整的事務(wù)執(zhí)行信息就會(huì)遺留在 AOF 文件里,當(dāng)重啟 Redis 時(shí),,程序會(huì)檢測(cè)到 AOF 文件并不完整,,Redis 會(huì)退出,并報(bào)告錯(cuò)誤,。需要使用 redis-check-aof 工具將部分成功的事務(wù)命令移除之后,,才能再次啟動(dòng)服務(wù)器。還原之后的數(shù)據(jù)總是一致的,,而且數(shù)據(jù)也是最新的(直到事務(wù)執(zhí)行之前為止),。

隔離性(Isolation)

Redis 是單進(jìn)程程序,并且它保證在執(zhí)行事務(wù)時(shí),,不會(huì)對(duì)事務(wù)進(jìn)行中斷,,事務(wù)可以運(yùn)行直到執(zhí)行完所有事務(wù)隊(duì)列中的命令為止。因此,,Redis 的事務(wù)是總是帶有隔離性的,。

持久性(Durability)

因?yàn)槭聞?wù)不過是用隊(duì)列包裹起了一組 Redis 命令,并沒有提供任何額外的持久性功能,,所以事務(wù)的持久性由 Redis 所使用的持久化模式?jīng)Q定:

  • 在單純的內(nèi)存模式下,,事務(wù)肯定是不持久的。

  • 在 RDB 模式下,,服務(wù)器可能在事務(wù)執(zhí)行之后,、RDB 文件更新之前的這段時(shí)間失敗,所以 RDB 模式下的 Redis 事務(wù)也是不持久的,。

  • 在 AOF 的“總是 SYNC ”模式下,,事務(wù)的每條命令在執(zhí)行成功之后,,都會(huì)立即調(diào)用 fsync 或 fdatasync 將事務(wù)數(shù)據(jù)寫入到 AOF 文件。但是,,這種保存是由后臺(tái)線程進(jìn)行的,,主線程不會(huì)阻塞直到保存成功,所以從命令執(zhí)行成功到數(shù)據(jù)保存到硬盤之間,,還是有一段非常小的間隔,,所以這種模式下的事務(wù)也是不持久的。

    其他 AOF 模式也和“總是 SYNC ”模式類似,,所以它們都是不持久的,。

小結(jié)

  • 事務(wù)提供了一種將多個(gè)命令打包,然后一次性,、有序地執(zhí)行的機(jī)制,。
  • 事務(wù)在執(zhí)行過程中不會(huì)被中斷,所有事務(wù)命令執(zhí)行完之后,,事務(wù)才能結(jié)束,。
  • 多個(gè)命令會(huì)被入隊(duì)到事務(wù)隊(duì)列中,然后按先進(jìn)先出(FIFO)的順序執(zhí)行,。
  • 帶 WATCH 命令的事務(wù)會(huì)將客戶端和被監(jiān)視的鍵在數(shù)據(jù)庫的 watched_keys 字典中進(jìn)行關(guān)聯(lián),,當(dāng)鍵被修改時(shí),程序會(huì)將所有監(jiān)視被修改鍵的客戶端的 REDIS_DIRTY_CAS 選項(xiàng)打開,。
  • 只有在客戶端的 REDIS_DIRTY_CAS 選項(xiàng)未被打開時(shí),,才能執(zhí)行事務(wù),否則事務(wù)直接返回失敗,。
  • Redis 的事務(wù)保證了 ACID 中的一致性(C)和隔離性(I),,但并不保證原子性(A)和持久性(D)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多