一個事務從開始到執(zhí)行會經(jīng)歷以下三個階段:
1. 開始事務。
2. 命令入隊。
3. 執(zhí)行事務。
MULTI 命令的執(zhí)行標記著事務的開始
? ? ? 這個命令唯一做的就是,將客戶端的REDIS_MULTI 選項打開,讓客戶端從非事務狀態(tài)切換到事務狀態(tài)。當客戶端進入事務狀態(tài)之后,客戶端發(fā)送的命令就會被放進事務隊列里。
? ? ? ?但其實并不是所有的命令都會被放進事務隊列,其中的例外就是EXEC 、DISCARD 、MULTI和WATCH 這四個命令——當這四個命令從客戶端發(fā)送到服務器時,它們會像客戶端處于非事務狀態(tài)一樣,直接被服務器執(zhí)行,如下圖所示:

對于以下事務隊列:

? ? ? ? 程序會首先執(zhí)行SET 命令,然后執(zhí)行GET 命令,再然后執(zhí)行SADD 命令,最后執(zhí)行SMEMBERS命令。執(zhí)行事務中的命令所得的結果會以FIFO 的順序保存到一個回復隊列中。
事務狀態(tài)下的DISCARD 、MULTI 和WATCH 命令
? ? ? ? ?DISCARD 命令用于取消一個事務,它清空客戶端的整個事務隊列,然后將客戶端從事務狀態(tài)調整回非事務狀態(tài)。
? ? ? ? ?WATCH 命令用于在事務開始之前監(jiān)視任意數(shù)量的鍵:當調用EXEC 命令執(zhí)行事務時,如果任意一個被監(jiān)視的鍵已經(jīng)被其他客戶端修改了,那么整個事務不再執(zhí)行,直接返回失敗。
以下執(zhí)行序列展示了上面的例子是如何失敗的:

? ? ? ?在時間T4 ,客戶端B 修改了name 鍵的值,當客戶端A 在T5 執(zhí)行EXEC 時,Redis 會發(fā)現(xiàn)name 這個被監(jiān)視的鍵已經(jīng)被修改,因此客戶端A 的事務不會被執(zhí)行,而是直接返回失敗。
WATCH 命令的實現(xiàn)
? ? ? 在每個代表數(shù)據(jù)庫的redis.h/redisDb 結構類型中,都保存了一個watched_keys 字典,字典的鍵是這個數(shù)據(jù)庫被監(jiān)視的鍵,也就是說watch監(jiān)控事務中的key。如果這個被監(jiān)視的key被改動,那么會將這個key的客戶端的REDIS_DIRTY_CAS打開,如圖:

? 當客戶端發(fā)送EXEC 命令、觸發(fā)事務執(zhí)行時,服務器會對客戶端的狀態(tài)進行檢查:
? ? ? ?如果客戶端的REDIS_DIRTY_CAS 選項已經(jīng)被打開,那么說明被客戶端監(jiān)視的鍵至少有一個已經(jīng)被修改了,事務的安全性已經(jīng)被破壞。服務器會放棄執(zhí)行這個事務,直接向客戶端返回空回復,表示事務執(zhí)行失敗。
? ? ? 如果REDIS_DIRTY_CAS 選項沒有被打開,那么說明所有監(jiān)視鍵都安全,服務器正式執(zhí)行事務。
事務的ACID 性質
? ? ? ?單個Redis 命令的執(zhí)行是原子性的,但Redis 沒有在事務上增加任何維持原子性的機制,所以Redis 事務的執(zhí)行并不是原子性的。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 如果一個事務隊列中的所有命令都被成功地執(zhí)行,那么稱這個事務執(zhí)行成功。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 另一方面,如果Redis 服務器進程在執(zhí)行事務的過程中被停止——比如接到KILL 信號、宿主機器停機,等等,那么事務執(zhí)行失敗。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?當事務失敗時,Redis 也不會進行任何的重試或者回滾動作。
一致性(Consistency)
Redis 的一致性問題可以分為三部分來討論:入隊錯誤、執(zhí)行錯誤、Redis 進程被終結。? ? ? ? ? ? ? ? ?
入隊錯誤
在命令入隊的過程中, 如果客戶端向服務器發(fā)送了錯誤的命令, 比如命令的參數(shù)數(shù)量不對,等等,那么服務器將向客戶端返回一個出錯信息,并且將客戶端的事務狀態(tài)設為REDIS_DIRTY_EXEC 。
? ? ? ?當客戶端執(zhí)行EXEC 命令時,Redis 會拒絕執(zhí)行狀態(tài)為REDIS_DIRTY_EXEC 的事務,并返回失敗信息。
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.
因此,帶有不正確入隊命令的事務不會被執(zhí)行,也不會影響數(shù)據(jù)庫的一致性。
執(zhí)行錯誤
? ? ? 如果命令在事務執(zhí)行的過程中發(fā)生錯誤,比如說,對一個不同類型的key 執(zhí)行了錯誤的操作。? ? ? ? ? 那么Redis 只會將錯誤包含在事務的結果中,這不會引起事務中斷或整個失敗,不會影響已執(zhí)行事務命令的結果,也不會影響后面要執(zhí)行的事務命令,所以它對事務的一致性也沒有影響。
Redis 進程被終結? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如果Redis 沒有采取任何持久化機制,那么重啟之后的數(shù)據(jù)庫總是空白的,如果有使用rdb或者aof,那么在此啟動會還原數(shù)據(jù)。
隔離性(Isolation)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Redis在執(zhí)行事務時,不會對事務進行中斷,事務可以運行直到執(zhí)行完所有事務隊列中的命令為止。因此,Redis 的事務是總是帶有隔離性的。
持久性(Durability)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?事務的持久性由Redis 所使用的持久化模式?jīng)Q定。
參考資料:《Redis的設計與實現(xiàn)》