一:簡介
Redis事務通常會使用MULTI,EXEC,WATCH等命令來完成,redis實現事務實現的機制與常見的關係型資料庫有很大的卻別,比如redis的事務不支援復原,事務執行時會阻塞其它用戶端的請求執行。 二:事務實現細節
redis事務從開始到結束通常會通過三個階段:
1.事務開始
2.命令入隊
3.事務執行
我們從下面的例子看下
redis > MULTI OKredis > SET "username" "bugall"QUEUEDredis > SET "password" 161616QUEUEDredis > GET "username"redis > EXEC1) ok2) "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" 161616redis > GET "username"
執行事務隊列裡的命令。
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 > MULTIOKredis > SET username "bugall"QUEUEDredis > EXEC1) OK2) "bugall"```與之相反,我們再來看一個事務執行失敗的例子。這個事務因為命令在放入事務隊列的時候被伺服器拒絕,所以事務中的所有命令都不會執行,因為前面我們有介紹到,redis的事務命令是統一先放到事務隊列裡,在使用者輸入EXEC命令的時候再統一執行。但是我們錯誤的使用"GET"命令,在命令放入事務隊列的時候被檢測到事務,這時候還沒有接收到EXEC命令,所以這個時候不牽扯到復原的問題,在EXEC的時候發現事務隊列裡有命令存在錯誤,所以事務裡的命令就全都不執行,這樣就達到裡事務的原子性,我們看下例子。```redis > MULTIOKredis > GET(error) ERR wrong number of arguments for 'get' commandredis > GET usernameQUEUEDredis > EXEC(error) EXECABORT Transaction discarded because of previous errors```redis的事務和傳統的關係型資料庫事務的最大區別在於,redis不支援事務的復原機制,即使事務隊列中的某個命令在執行期間出現錯誤,整個事務也會繼續執行下去,直到將事務隊列中的所有命令都執行完畢為止,我們看下面的例子```redis > SET username "bugall"OKredis > MULTIOKredis > SADD member "bugall" "litengfe" "yangyifang"QUEUEDredis > RPUSH username "b" "l" "y" //錯誤對鍵username使用列表鍵命令QUEUEDredis > SADD password "123456" "123456" "123456"QUEUEDredis > EXEC1) (integer) 32) (error) WRONGTYPE Operation against a key holding the wrong kind of value3) (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時,程式會每秒同步一次命令資料到磁碟因為停機可能會恰好發生在等待同步的那一秒內,這種可能造成交易資料丟失,所以這種配置下的事務不具有耐久性
轉自:bugall的sf