事務可以1次履行多個命令, 并且帶有以下兩個重要的保證:
事務是1個單獨的隔離操作:事務中的所有命令都會序列化、按順序地履行。事務在履行的進程中,不會被其他客戶端發送來的命令要求所打斷。
事務是1個原子操作:事務中的命令要末全部被履行,要末全部都不履行。
EXEC 命令負責觸發并履行事務中的所有命令:
當使用 AOF 方式做持久化的時候, Redis 會使用單個 write(2) 命令將事務寫入到磁盤中。
但是,如果 Redis http://www.vxbq.cn/server/由于某些緣由被管理員殺死,或遇上某種硬件故障,那末可能只有部份事務命令會被成功寫入到磁盤中。
如果 Redis 在重新啟動時發現 AOF 文件出了這樣的問題,那末它會退出,并匯報1個毛病。
使用 redis-check-aof 程序可以修復這1問題:它會移除 AOF 文件中不完全事務的信息,確保http://www.vxbq.cn/server/可以順利啟動。
從 2.2 版本開始,Redis 還可以通過樂觀鎖(optimistic lock)實現 CAS (check-and-set)操作,具體信息請參考文檔的后半部份。
MULTI 命令用于開啟1個事務,它總是返回 OK 。
MULTI 履行以后, 客戶端可以繼續向http://www.vxbq.cn/server/發送任意多條命令, 這些命令不會立即被履行, 而是被放到1個隊列中, 當 EXEC 命令被調用時, 所有隊列中的命令才會被履行。
另外一方面, 通過調用 DISCARD , 客戶端可以清空事務隊列, 并放棄履行事務。
以下是1個事務例子, 它原子地增加了 foo 和 bar 兩個鍵的值:
> MULTI OK > INCR foo QUEUED > INCR bar QUEUED > EXEC 1) (integer) 1 2) (integer) 1
EXEC 命令的回復是1個數組, 數組中的每一個元素都是履行事務中的命令所產生的回復。 其中, 回復元素的前后順序和命令發送的前后順序1致。
當客戶端處于事務狀態時, 所有傳入的命令都會返回1個內容為 QUEUED 的狀態回復(status reply), 這些被入隊的命令將在 EXEC命令被調用時履行。
使用事務時可能會遇上以下兩種毛病:
對產生在 EXEC 履行之前的毛病,客戶端之前的做法是檢查命令入隊所得的返回值:如果命令入隊時返回 QUEUED ,那末入隊成功;否則,就是入隊失敗。如果有命令在入隊時失敗,那末大部份客戶端都會停止并取消這個事務。
不過,從 Redis 2.6.5 開始,http://www.vxbq.cn/server/會對命令入隊失敗的情況進行記錄,并在客戶端調用 EXEC 命令時,謝絕履行并自動放棄這個事務。
在 Redis 2.6.5 之前, Redis 只履行事務中那些入隊成功的命令,而疏忽那些入隊失敗的命令。 而新的處理方式則使得在流水線(pipeline)中包括事務變得簡單,由于發送事務和讀取事務的回復都只需要和http://www.vxbq.cn/server/進行1次通訊。
至于那些在 EXEC 命令履行以后所產生的毛病, 并沒有對它們進行特別處理: 即便事務中有某個/某些命令在履行時產生了毛病, 事務中的其他命令依然會繼續履行。
從協議的角度來看這個問題,會更容易理解1些。 以下例子中, LPOP 命令的履行將出錯, 雖然調用它的語法是正確的:
Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. MULTI +OK SET a 3 abc +QUEUED LPOP a +QUEUED EXEC *2 +OK -ERR Operation against a key holding the wrong kind of value
EXEC 返回兩條批量回復(bulk reply): 第1條是 OK ,而第2條是 -ERR 。 至于怎樣用適合的方法來表示事務中的毛病, 則是由客戶端自己決定的。
最重要的是記住這樣1條, 即便事務中有某條/某些命令履行失敗了, 事務隊列中的其他命令依然會繼續履行 ―― Redis 不會停止履行事務中的命令。
以下例子展現的是另外一種情況, 當命令在入隊時產生毛病, 毛病會立即被返回給客戶端:
MULTI +OK INCR a b c -ERR wrong number of arguments for 'incr' command
由于調用 INCR 命令的參數格式不正確, 所以這個 INCR 命令入隊失敗。
如果你有使用關系式http://www.vxbq.cn/db/的經驗, 那末 “Redis 在事務失敗時不進行回滾,而是繼續履行余下的命令”這類做法可能會讓你覺得有點奇怪。
以下是這類做法的優點:
有種觀點認為 Redis 處理事務的做法會產生 bug , 但是需要注意的是, 在通常情況下, 回滾其實不能解決編程毛病帶來的問題。 舉個例子, 如果你本來想通過 INCR 命令將鍵的值加上 1 , 卻不謹慎加上了 2 , 又或對毛病類型的鍵履行了 INCR , 回滾是沒有辦法處理這些情況的。
鑒于沒有任何機制能避免http://www.vxbq.cn自己釀成的毛病, 并且這類毛病通常不會在生產環境中出現, 所以 Redis 選擇了更簡單、更快速的無回滾方式來處理事務。
當履行 DISCARD 命令時, 事務會被放棄, 事務隊列會被清空, 并且客戶端會從事務狀態中退出:
redis> SET foo 1 OK redis> MULTI OK redis> INCR foo QUEUED redis> DISCARD OK redis> GET foo "1"
WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行動。
被 WATCH 的鍵會被監視,并會發覺這些鍵是不是被改動過了。 如果有最少1個被監視的鍵在 EXEC 履行之前被修改了, 那末全部事務都會被取消, EXEC 返回空多條批量回復(null multi-bulk reply)來表示事務已失敗。
舉個例子, 假定我們需要原子性地為某個值進行增 1 操作(假定 INCR 不存在)。
首先我們可能會這樣做:
val = GET mykey val = val + 1 SET mykey $val
上面的這個實現在只有1個客戶真個時候可以履行得很好。 但是, 當多個客戶端同時對同1個鍵進行這樣的操作時, 就會產生競爭條件。
舉個例子, 如果客戶端 A 和 B 都讀取了鍵原來的值, 比如 10 , 那末兩個客戶端都會將鍵的值設為 11 , 但正確的結果應當是 12才對。
有了 WATCH , 我們就能夠輕松地解決這類問題了:
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
使用上面的代碼, 如果在 WATCH 履行以后, EXEC 履行之前, 有其他客戶端修改了 mykey 的值, 那末當前客戶真個事務就會失敗。 程序需要做的, 就是不斷重試這個操作, 直到沒有產生碰撞為止。
這類情勢的鎖被稱作樂觀鎖, 它是1種非常強大的鎖機制。 并且由于大多數情況下, 不同的客戶端會訪問不同的鍵, 碰撞的情況1般都很少, 所以通常其實不需要進行重試。
WATCH 使得 EXEC 命令需要有條件地履行: 事務只能在所有被監視鍵都沒有被修改的條件下履行, 如果這個條件不能滿足的話,事務就不會被履行。
如果你使用 WATCH 監視了1個帶過期時間的鍵, 那末即便這個鍵過期了, 事務依然可以正常履行, 關于這方面的詳細情況,請看這個帖子: http://code.google.com/p/redis/issues/detail?id=270
WATCH 命令可以被調用屢次。 對鍵的監視從 WATCH 履行以后開始生效, 直到調用 EXEC 為止。
用戶還可以在單個 WATCH 命令中監視任意多個鍵, 就像這樣:
redis> WATCH key1 key2 key3 OK
當 EXEC 被調用時, 不管事務是不是成功履行, 對所有鍵的監視都會被取消。
另外, 當客戶端斷開連接時, 該客戶端對鍵的監視也會被取消。
使用無參數的 UNWATCH 命令可以手動取消對所有鍵的監視。 對1些需要改動多個鍵的事務, 有時候程序需要同時對多個鍵進行加鎖, 然后檢查這些鍵確當前值是不是符合程序的要求。 當值達不到要求時, 就能夠使用 UNWATCH 命令來取消目前對鍵的監視, 中途放棄這個事務, 并等待事務的下次嘗試。
WATCH 可以用于創建 Redis 沒有內置的原子操作。
舉個例子, 以下代碼實現了原創的 ZPOP 命令, 它可以原子地彈出有序集合中分值(score)最小的元素:
WATCH zset element = ZRANGE zset 0 0 MULTI ZREM zset element EXEC
程序只要重復履行這段代碼, 直到 EXEC 的返回值不是空多條回復(null multi-bulk reply)便可。
從定義上來講, Redis 中的腳本本身就是1種事務, 所以任何在事務里可以完成的事, 在腳本里面也能完成。 并且1般來講, 使用腳本要來得更簡單,并且速度更快。
由于腳本功能是 Redis 2.6 才引入的, 而事務功能則更早之前就存在了, 所以 Redis 才會同時存在兩種處理事務的方法。
不過我們其實不打算在短時間內就移除事務功能, 由于事務提供了1種即便不使用腳本, 也能夠避免競爭條件的方法, 而且事務本身的實現其實不復雜。
不過在不遠的將來, 可能所有用戶都會只使用腳本來實現事務也說不定。 如果真的產生這類情況的話, 那末我們將廢棄并終究移除事務功能。
上一篇 《程序員跳槽全攻略》讀后總結
下一篇 jquery-osx