Redis通過MULTI、EXEC、WATCH等命令來實現事務(transaction)功能。事務提供了一種將多個命令請求打包,然后一次性、按順序地執行多個命令的機制,并且在事務執行期間,服務器不會中斷事務而去執行其他客戶端命令。
1 事務的實現
??一個事務從開始到結束通常經歷三個階段:
(1) 事務開始
(2) 命令入隊
(3) 事務執行
??1.1 事務開始
MULTI命令的執行標志著事務的開始:
127.0.0.1:6379> MULTIOK
MULTI命令可以將執行該命令的客戶端從非事務狀態切換至事務狀態。
??1.2 命令入隊
??當一個客戶端處于非事務狀態時,這個客戶端的命令會立即被服務器執行。與此不同的是,當一個客戶端切換到事務狀態之后,服務器會根據這個客戶端發來的不同的命令執行不同的操作:
(1) 如果客戶端發送的命令為EXEC、DISCARD、WATCH、MULTI四個命令中的其中一個,那么服務器會立即執行這個命令。
(2) 如果客戶端發送的命令是EXEC、DISCARD、WATCH、MULTI四個命令以為的其他命令,那么服務器不會立即執行這個命令,而是將這個命令放在一個事務隊列中,然后向客戶端返回QUEUED回復。
服務器判斷命令是該入隊還是該執行的過程
??1.3 事務隊列
??事務隊列是一個以先進先出(FIFO)的方式保存入隊的命令,較先入隊的命令會被放到數組的前面,而較后入隊的命令則會被放到數組的后面。例如:
127.0.0.1:6379>> MULTIOK127.0.0.1:6379>> set name Redis;
127.0.0.1:6379>> get name;
127.0.0.1:6379>> set author"Peter Seibei";
127.0.0.1:6379>> get author;
那么服務器器將會為客戶端創建一個事務隊列,將上面4條命令入隊,最先入隊的SET命令放在事務隊列的最前面……:
事務隊列
??1.4 執行事務
當一個處于事務狀態的客戶端向服務器發送EXEC命令時,這個EXEC命令將立即被服務器執行。服務器會遍歷這個客戶端的事務隊列,執行隊列中保存的所有的命令,最后將執行命令所得的結果返回給客戶端。對于上例子:
127.0.0.1:6379> EXEC1)
OK2)
"Redis"3)
OK4)
"Peter Seibei"
2 WATCH命令的實現
WATCH命令是一個樂觀所(optimistic locking),它可以在EXEC命令執行前,監視任意數量的數據庫鍵,并在EXEC命令執行時,檢查監視的鍵是否至少有一個已經被修改過了,如果修改過了,服務器將拒絕執行事務,并向客戶端返回代表事務執行失敗的空回復。例如,下面的事務將會執行失敗:
127.0.0.1:6379>> WATCH
?nameOK
127.0.0.1:6379>> MULTI
OK
127.0.0.1:6379>> set"name""Peter"
127.0.0.1:6379>> EXEC(nil)
時間客戶端A客戶端B
T1WATCH "name"
T2MULTI
T3SET "name" "Peter"
T4SET "name" "jack"
T5EXEC
在時間T4,客戶端B修改了"name"鍵的值,當客戶端A在T5執行EXEC命令時,服務器會發現WATCH監視的鍵“name”已經被修改,因此服務器拒絕執行客戶端A的事務,并向客戶端A返回空回復。
??2.1使用WATCH命令監視數據庫鍵
每個Redis數據庫保存著一個watched_keys字典,這個字典的鍵是某個被WATCH命令監視的數據庫鍵,而字典的值是一個鏈表,鏈表記錄了所有監視相應數據庫鍵的客戶端。
一個watched_keys字典
上圖表明:
(1) 客戶端c1和c2正在監視鍵“name”。
(2) 客戶端c3正在監視鍵“age”。
(3) 客戶端c2和c4正在監視鍵“address”。
??2.2 監視機制的觸發
所有對數據庫進行修改命令,如SET、LPUSH、SADD、ZREM、DEL等,在執行后都會對watched_keys字典進行檢查,查看被修改的數據庫鍵是否是被客戶端所監視的鍵,如果有的話,客戶端REDIS_DIRTY_CAS標識將會被打開,表示該客戶端的事務安全性已經被破壞。
??2.3 判斷事務是否安全
當服務器接收到一個客戶端發來的EXEC命令,服務器會根據這個客戶端是否打開了REDIS_DIRTY_CAS標識來決定是否執行事務:
(1) 如果客戶端的REDIS_DIRTY_ CAS標識已經被打開,那么說明客戶端所監視的鍵當中,至少有一個鍵已經被修改過了,在這種情況下,客戶端提交的事務已經不再安全,所以服務器拒絕執行客戶端提交的事務。
(2) 如果客戶端的REDIS_DIRTY_CAS標識沒有被打開,那么說明客戶端監視的所有鍵都沒有被修改過(或者客戶端沒有監視任何鍵),事務仍然是安全的,服務器將執行客戶端提交的這個事務。
3 事務的ACID性質
??在Redis中,事務總是具有原子性(Atomicity)、一致性(Consistency)和隔離性(Isolation),并且當Redis運行在某種特定的持久化模式下,事務也具有耐久性(Durability)。
??3.1 原子性
事務具有原子性指的是, 數據庫將事務中的多個操作當作一個整體來執行,服務器要么就執行事務中的所有操作, 要么就一個操作也不執行。
對于Redis的事務功能來說,事務隊列中的命令要么就全部都執行,要么就一個都不執行,因此, Redis的事務是具有原子性的。
下面是一個執行失敗的事務,這個事務因為命令入隊出錯而被服務器拒絕執行:
127.0.0.1:6379>multi
OK
127.0.0.1:6379>set msg hello
127.0.0.1:6379>get(error)ERRwrong number of argumentsfor'get'command
127.0.0.1:6379>get msg
127.0.0.1:6379>exec
(error) EXEC ABORT Transactiondiscarded because of previous errors.
Redis的事務和傳統的關系型數據庫事務的最大區別在于,Redis不支持事務回滾機制(rollback), 即使事務隊列中的某個命令在執行期間出現了錯誤,整個事務也會繼續執行下去,直到將事務隊列中的所有命令都執行完畢為止。
下面展示了即使RPUSH命令在執行期間出現了錯誤,事務的后續命令也會繼續執行下去, 并且之前執行的命令也不會有任何影響:
127.0.0.1:6379> set msg hello
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd fruit apple banana cherry
127.0.0.1:6379> rpush msg bye redis
127.0.0.1:6379> sadd alphabet a b c
127.0.0.1:6379> exec
1)(integer)3
2)(error)WRONGTYPE Operation against a key holding the wrong kind of value
3)(integer)3
??Redis為什么不支持回滾:不支持事務回滾是因為這種復雜的功能和Redis追求簡單高效的設計主旨不相符,并且Redis事務的執行時錯誤通常都是編程錯誤產生的, 這種錯誤通常只會出現在開發環境中, 而很少會在實際的生產環境中出現。
??3.2 一致性
??事務的一致性是指,如果數據庫執行前是一致的,那么在事務執行后,無論事務是否執行成功,數據庫也應該是一致的。
??3.2.1入隊錯誤
如果一個事務在入隊命令的過程中,出現了命令不存在,或者命令的格式不正確等情況, 那么Redis將拒絕執行這個事務。因為服務器會拒絕執行人隊過程中出現錯誤的事務, 所以Redis事務的一致性不會被帶有入隊錯誤的事務影響。
127.0.0.1:6379>multi
OK
127.0.0.1:6379>set msg?
hello
127.0.0.1:6379>YAHOOO
(error)ERRunknown command'YAHOOO'
127.0.0.1:6379>get msg
127.0.0.1:6379>exec
(error)EXECABORTTransactiondiscarded because of previous errors.
Redis 2.6.5以前的入隊錯誤處理:Redis會忽略錯誤的命令,而正確的命令如上面的SET和GET仍然會被執行。
??3.2.2 執行錯誤
執行錯誤通常都是一些不能在入隊時被服務器發現的錯誤, 這些錯誤只會在命令實際執行時被觸發。即使在事務的執行過程中發生了錯誤, 服務器也不會中斷事務的執行, 它會繼續執行事務中余下的其他命令, 并且已執行的命令(包括執行命令所產生的結果)不會被出錯的命令影響。
因為在事務執行的過程中, 出錯的命令會被服務器識別出來, 并進行相應的錯誤處理, 所以這些出錯命令不會對數據庫做任何修改, 也不會對事務的一致性產生任何影響。
127.0.0.1:6379> set msg hello
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd fruit apple banana cherryQUEUED127.0.0.1:6379> rpush msg bye redis
QUEUED
127.0.0.1:6379> sadd alphabet a b c
QUEUED
127.0.0.1:6379> exec
1)(integer)3
2)(error)WRONGTYPE Operation against a key holding the wrong kind of value
3)(integer)3
??3.2.3 服務器停機
(1) 如果服務器運行在無持久化的內存模式下,那么重啟之后的數據庫將是空白的, 因此數據總是一致的。
(2) 如果服務器運行在RDB模式或AOF模式下, 那么在事務中途停機不會導致不一致,因為服務器可以根據RDB文件或AOF文件來回復數據,從而將數據庫還原到一個一致的狀態。
??3.3 隔離性
事務的隔離性指的是,即使數據庫中有多個事務并發地執行,各個事務之間也不會互相 影響,并且在并發狀態下執行的事務和串行執行的事務產生的結果完全相同。
因為Redis使用單線程的方式來執行事務(以及事務隊列中的命令),并且服務器保證, 在執行事務期間不會對事務進行中斷,因此,Redis的事務總是以串行的方式運行的,并且 事務也總是具有隔離性的。
??3.4 耐久性
事務的耐久性指的是,當一個事務執行完畢時,執行這個事務所得的結果巳經被保存到 永久性存儲介質(比如硬盤)里面了, 即使服務器在事務執行完畢 之后停機, 執行事務所得的結果也不會丟失。Redis事務的耐久性由服務器所使用持久化模式決定的:
(1) 當服務器在無持久化的內存模式下運作時,事務不具有耐久性。因為一旦服務器停機,
服務器所有的數據都將丟失。
(2) 當服務器在ROB持久化模式下運作時,事務同樣不具有耐久性。因為服務器只會在特定的保存條件下才會執行BGSAVE命令,并且異步執行的BGSAVE命令不能保證事務的數據第一時間被保存到硬盤上。
(3) 當服務器運行在AOF持久化模式下,并且appendfsync選項的值為always時,程序總會在執行命令之后調用同步(sync)函數,將命令數據真正地保存到硬盤里。
4 小結
(1) 事務提供了一種將多個命令打包,然后一次性、有序地執行的機制。
(2) 多個命令會被人隊到事務隊列中, 然后按先進先出(FIFO)的順序執行。
(3) 事務在執行過程中不會被中斷,當事務隊列中的所有命令都被執行完畢之后,事務
才會結束。
(4) 帶有WATCH命令的事務會將客戶端和被監視的鍵在數據庫的watched_keys字典關聯,當鍵被修改時,程序會將所有監視被修改鍵的客戶端的REDIS_DIRTY_CAS標識打開,服務只有在REDIS_DIRTY_CAS標識沒有打開時,才會執行客戶端提交的事務,否則服務器拒絕執行事務。
(5) Redis事務不支持回滾機制。
(6) Redis的事務總是具有ACID中的原子性、一致性和隔離性,當服務器運行在AOF持久化模式下,并且appendfsync選項的值為always時,事務也具有耐久性。