redis事務實現原理(源碼分析)

Author: bugall
Wechat: bugallF
Email: 769088641@qq.com
Github: https://github.com/bugallgit

一:簡介

Redis事務一般會使用MULTI,EXEC,WATCH等命令來完成,redis實現事務實現的機制與常見的關係型數據庫有很大的區別,好比redis的事務不支持回滾,事務執行時會阻塞其它客戶端的請求執行。github

二:事務實現細節

redis事務從開始到結束一般會經過三個階段:redis

1.事務開始數據庫

2.命令入隊編程

3.事務執行數組

咱們從下面的例子看下安全

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);
}

在打開事務標識的客戶端裏,這些命令,都會被暫存到一個命令隊列裏,不會由於用戶會的輸入而當即執行併發

redis > SET "username" "bugall"
redis > SET "password" 161616
redis > GET "username"

執行事務隊列裏的命令。app

redis > EXEC

這裏須要注意的是,在客戶端打開了事務標識後,只有命令: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;

事務隊列以先進先出的保存方法,較先入隊的命令會被放到數組的前面,而較後入隊的命令則會被放到數組的後面.

三:執行事務

當開啓事務標識的客戶端發送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命令時判斷是否至少有一個被watch的鍵值

被修改若是被修改就放棄事務的執行,若是沒有被修改就清空watch的信息,執行事務列表裏的命令。
unwatch:

顧名思義能夠看出它的功能是與watch相反的,是取消對一個鍵值的「監聽」的功能能

discard:

清空客戶端的事務隊列裏的全部命令,並取消客戶端的事務標記,若是客戶端在執行事務的時候watch了一些鍵,則discard會取消全部

鍵的watch.

五:redis事務的ACID特性

在傳統的關係型數據庫中,嚐嚐用ACID特質來檢測事務功能的可靠性和安全性。

在redis中事務老是具備原子性(Atomicity),一致性(Consistency)和隔離性(Isolation),而且當redis運行在某種特定的持久化
模式下,事務也具備耐久性(Durability).

①原子性

事務具備原子性指的是,數據庫將事務中的多個操做看成一個總體來執行,服務器要麼就執行事務中的全部操做,要麼就一個操做也不執行。

可是對於redis的事務功能來講,事務隊列中的命令要麼就所有執行,要麼就一個都不執行,所以redis的事務是具備原子性的。咱們一般會知道

兩種關於redis事務原子性的說法,一種是要麼事務都執行,要麼都不執行。另一種說法是redis事務當事務中的命令執行失敗後面的命令還
會執行,錯誤以前的命令不會回滾。其實這個兩個說法都是正確的。可是缺一不可。咱們接下來具體分析下

咱們先看一個能夠正確執行的事務例子
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時,程序會每秒同步一次命令數據到磁盤由於停機可能會剛好發生在等待同步的那一秒內,這種可能形成事務數據丟失,因此這種配置下的事務不具備耐久性
相關文章
相關標籤/搜索