Redis事务(transaction)
Redis事務(wù)(transaction)
本文檔翻譯自:?http://redis.io/topics/transactions?。
MULTI?、?EXEC?、?DISCARD?和?WATCH?是 Redis 事務(wù)的基礎(chǔ)。
事務(wù)可以一次執(zhí)行多個(gè)命令, 并且?guī)в幸韵聝蓚€(gè)重要的保證:
-
事務(wù)是一個(gè)單獨(dú)的隔離操作:事務(wù)中的所有命令都會(huì)序列化、按順序地執(zhí)行。事務(wù)在執(zhí)行的過程中,不會(huì)被其他客戶端發(fā)送來的命令請求所打斷。
-
事務(wù)是一個(gè)原子操作:事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。
EXEC?命令負(fù)責(zé)觸發(fā)并執(zhí)行事務(wù)中的所有命令:
- 如果客戶端在使用?MULTI?開啟了一個(gè)事務(wù)之后,卻因?yàn)閿嗑€而沒有成功執(zhí)行?EXEC?,那么事務(wù)中的所有命令都不會(huì)被執(zhí)行。
- 另一方面,如果客戶端成功在開啟事務(wù)之后執(zhí)行?EXEC?,那么事務(wù)中的所有命令都會(huì)被執(zhí)行。
當(dāng)使用 AOF 方式做持久化的時(shí)候, Redis 會(huì)使用單個(gè)?write(2)?命令將事務(wù)寫入到磁盤中。
然而,如果 Redis 服務(wù)器因?yàn)槟承┰虮还芾韱T殺死,或者遇上某種硬件故障,那么可能只有部分事務(wù)命令會(huì)被成功寫入到磁盤中。
如果 Redis 在重新啟動(dòng)時(shí)發(fā)現(xiàn) AOF 文件出了這樣的問題,那么它會(huì)退出,并匯報(bào)一個(gè)錯(cuò)誤。
使用?redis-check-aof?程序可以修復(fù)這一問題:它會(huì)移除 AOF 文件中不完整事務(wù)的信息,確保服務(wù)器可以順利啟動(dòng)。
從 2.2 版本開始,Redis 還可以通過樂觀鎖(optimistic lock)實(shí)現(xiàn) CAS (check-and-set)操作,具體信息請參考文檔的后半部分。
用法
MULTI?命令用于開啟一個(gè)事務(wù),它總是返回?OK?。
MULTI?執(zhí)行之后, 客戶端可以繼續(xù)向服務(wù)器發(fā)送任意多條命令, 這些命令不會(huì)立即被執(zhí)行, 而是被放到一個(gè)隊(duì)列中, 當(dāng)?EXEC?命令被調(diào)用時(shí), 所有隊(duì)列中的命令才會(huì)被執(zhí)行。
另一方面, 通過調(diào)用?DISCARD?, 客戶端可以清空事務(wù)隊(duì)列, 并放棄執(zhí)行事務(wù)。
以下是一個(gè)事務(wù)例子, 它原子地增加了?foo?和?bar?兩個(gè)鍵的值:
> MULTI OK> INCR foo QUEUED> INCR bar QUEUED> EXEC 1) (integer) 1 2) (integer) 1EXEC?命令的回復(fù)是一個(gè)數(shù)組, 數(shù)組中的每個(gè)元素都是執(zhí)行事務(wù)中的命令所產(chǎn)生的回復(fù)。 其中, 回復(fù)元素的先后順序和命令發(fā)送的先后順序一致。
當(dāng)客戶端處于事務(wù)狀態(tài)時(shí), 所有傳入的命令都會(huì)返回一個(gè)內(nèi)容為?QUEUED?的狀態(tài)回復(fù)(status reply), 這些被入隊(duì)的命令將在?EXEC?命令被調(diào)用時(shí)執(zhí)行。
事務(wù)中的錯(cuò)誤
使用事務(wù)時(shí)可能會(huì)遇上以下兩種錯(cuò)誤:
- 事務(wù)在執(zhí)行?EXEC?之前,入隊(duì)的命令可能會(huì)出錯(cuò)。比如說,命令可能會(huì)產(chǎn)生語法錯(cuò)誤(參數(shù)數(shù)量錯(cuò)誤,參數(shù)名錯(cuò)誤,等等),或者其他更嚴(yán)重的錯(cuò)誤,比如內(nèi)存不足(如果服務(wù)器使用?maxmemory?設(shè)置了最大內(nèi)存限制的話)。
- 命令可能在?EXEC?調(diào)用之后失敗。舉個(gè)例子,事務(wù)中的命令可能處理了錯(cuò)誤類型的鍵,比如將列表命令用在了字符串鍵上面,諸如此類。
對于發(fā)生在?EXEC?執(zhí)行之前的錯(cuò)誤,客戶端以前的做法是檢查命令入隊(duì)所得的返回值:如果命令入隊(duì)時(shí)返回?QUEUED?,那么入隊(duì)成功;否則,就是入隊(duì)失敗。如果有命令在入隊(duì)時(shí)失敗,那么大部分客戶端都會(huì)停止并取消這個(gè)事務(wù)。
不過,從 Redis 2.6.5 開始,服務(wù)器會(huì)對命令入隊(duì)失敗的情況進(jìn)行記錄,并在客戶端調(diào)用?EXEC?命令時(shí),拒絕執(zhí)行并自動(dòng)放棄這個(gè)事務(wù)。
在 Redis 2.6.5 以前, Redis 只執(zhí)行事務(wù)中那些入隊(duì)成功的命令,而忽略那些入隊(duì)失敗的命令。 而新的處理方式則使得在流水線(pipeline)中包含事務(wù)變得簡單,因?yàn)榘l(fā)送事務(wù)和讀取事務(wù)的回復(fù)都只需要和服務(wù)器進(jìn)行一次通訊。
至于那些在?EXEC?命令執(zhí)行之后所產(chǎn)生的錯(cuò)誤, 并沒有對它們進(jìn)行特別處理: 即使事務(wù)中有某個(gè)/某些命令在執(zhí)行時(shí)產(chǎn)生了錯(cuò)誤, 事務(wù)中的其他命令仍然會(huì)繼續(xù)執(zhí)行。
從協(xié)議的角度來看這個(gè)問題,會(huì)更容易理解一些。 以下例子中,?LPOP?命令的執(zhí)行將出錯(cuò), 盡管調(diào)用它的語法是正確的:
Trying 127.0.0.1... Connected to localhost. Escape character is '^]'.MULTI +OKSET a 3 abc+QUEUED LPOP a+QUEUED EXEC*2 +OK -ERR Operation against a key holding the wrong kind of valueEXEC?返回兩條批量回復(fù)(bulk reply): 第一條是?OK?,而第二條是?-ERR?。 至于怎樣用合適的方法來表示事務(wù)中的錯(cuò)誤, 則是由客戶端自己決定的。
最重要的是記住這樣一條, 即使事務(wù)中有某條/某些命令執(zhí)行失敗了, 事務(wù)隊(duì)列中的其他命令仍然會(huì)繼續(xù)執(zhí)行 —— Redis 不會(huì)停止執(zhí)行事務(wù)中的命令。
以下例子展示的是另一種情況, 當(dāng)命令在入隊(duì)時(shí)產(chǎn)生錯(cuò)誤, 錯(cuò)誤會(huì)立即被返回給客戶端:
MULTI +OKINCR a b c -ERR wrong number of arguments for 'incr' command因?yàn)檎{(diào)用?INCR?命令的參數(shù)格式不正確, 所以這個(gè)?INCR?命令入隊(duì)失敗。
為什么 Redis 不支持回滾(roll back)
如果你有使用關(guān)系式數(shù)據(jù)庫的經(jīng)驗(yàn), 那么 “Redis 在事務(wù)失敗時(shí)不進(jìn)行回滾,而是繼續(xù)執(zhí)行余下的命令”這種做法可能會(huì)讓你覺得有點(diǎn)奇怪。
以下是這種做法的優(yōu)點(diǎn):
- Redis 命令只會(huì)因?yàn)殄e(cuò)誤的語法而失敗(并且這些問題不能在入隊(duì)時(shí)發(fā)現(xiàn)),或是命令用在了錯(cuò)誤類型的鍵上面:這也就是說,從實(shí)用性的角度來說,失敗的命令是由編程錯(cuò)誤造成的,而這些錯(cuò)誤應(yīng)該在開發(fā)的過程中被發(fā)現(xiàn),而不應(yīng)該出現(xiàn)在生產(chǎn)環(huán)境中。
- 因?yàn)椴恍枰獙貪L進(jìn)行支持,所以 Redis 的內(nèi)部可以保持簡單且快速。
有種觀點(diǎn)認(rèn)為 Redis 處理事務(wù)的做法會(huì)產(chǎn)生 bug , 然而需要注意的是, 在通常情況下, 回滾并不能解決編程錯(cuò)誤帶來的問題。 舉個(gè)例子, 如果你本來想通過?INCR?命令將鍵的值加上?1?, 卻不小心加上了?2?, 又或者對錯(cuò)誤類型的鍵執(zhí)行了?INCR?, 回滾是沒有辦法處理這些情況的。
鑒于沒有任何機(jī)制能避免程序員自己造成的錯(cuò)誤, 并且這類錯(cuò)誤通常不會(huì)在生產(chǎn)環(huán)境中出現(xiàn), 所以 Redis 選擇了更簡單、更快速的無回滾方式來處理事務(wù)。
放棄事務(wù)
當(dāng)執(zhí)行?DISCARD?命令時(shí), 事務(wù)會(huì)被放棄, 事務(wù)隊(duì)列會(huì)被清空, 并且客戶端會(huì)從事務(wù)狀態(tài)中退出:
redis> SET foo 1 OKredis> MULTI OKredis> INCR foo QUEUEDredis> DISCARD OKredis> GET foo "1"使用 check-and-set 操作實(shí)現(xiàn)樂觀鎖
WATCH?命令可以為 Redis 事務(wù)提供 check-and-set (CAS)行為。
被?WATCH?的鍵會(huì)被監(jiān)視,并會(huì)發(fā)覺這些鍵是否被改動(dòng)過了。 如果有至少一個(gè)被監(jiān)視的鍵在?EXEC?執(zhí)行之前被修改了, 那么整個(gè)事務(wù)都會(huì)被取消,?EXEC?返回空多條批量回復(fù)(null multi-bulk reply)來表示事務(wù)已經(jīng)失敗。
舉個(gè)例子, 假設(shè)我們需要原子性地為某個(gè)值進(jìn)行增?1?操作(假設(shè)?INCR?不存在)。
首先我們可能會(huì)這樣做:
val = GET mykey val = val + 1 SET mykey $val上面的這個(gè)實(shí)現(xiàn)在只有一個(gè)客戶端的時(shí)候可以執(zhí)行得很好。 但是, 當(dāng)多個(gè)客戶端同時(shí)對同一個(gè)鍵進(jìn)行這樣的操作時(shí), 就會(huì)產(chǎn)生競爭條件。
舉個(gè)例子, 如果客戶端 A 和 B 都讀取了鍵原來的值, 比如?10?, 那么兩個(gè)客戶端都會(huì)將鍵的值設(shè)為?11?, 但正確的結(jié)果應(yīng)該是?12?才對。
有了?WATCH?, 我們就可以輕松地解決這類問題了:
WATCH mykeyval = GET mykey val = val + 1MULTI SET mykey $val EXEC使用上面的代碼, 如果在?WATCH?執(zhí)行之后,?EXEC?執(zhí)行之前, 有其他客戶端修改了?mykey?的值, 那么當(dāng)前客戶端的事務(wù)就會(huì)失敗。 程序需要做的, 就是不斷重試這個(gè)操作, 直到?jīng)]有發(fā)生碰撞為止。
這種形式的鎖被稱作樂觀鎖, 它是一種非常強(qiáng)大的鎖機(jī)制。 并且因?yàn)榇蠖鄶?shù)情況下, 不同的客戶端會(huì)訪問不同的鍵, 碰撞的情況一般都很少, 所以通常并不需要進(jìn)行重試。
了解 WATCH
WATCH?使得?EXEC?命令需要有條件地執(zhí)行: 事務(wù)只能在所有被監(jiān)視鍵都沒有被修改的前提下執(zhí)行, 如果這個(gè)前提不能滿足的話,事務(wù)就不會(huì)被執(zhí)行。
如果你使用?WATCH?監(jiān)視了一個(gè)帶過期時(shí)間的鍵, 那么即使這個(gè)鍵過期了, 事務(wù)仍然可以正常執(zhí)行, 關(guān)于這方面的詳細(xì)情況,請看這個(gè)帖子:?http://code.google.com/p/redis/issues/detail?id=270
WATCH?命令可以被調(diào)用多次。 對鍵的監(jiān)視從?WATCH?執(zhí)行之后開始生效, 直到調(diào)用?EXEC?為止。
用戶還可以在單個(gè)?WATCH?命令中監(jiān)視任意多個(gè)鍵, 就像這樣:
redis> WATCH key1 key2 key3 OK當(dāng)?EXEC?被調(diào)用時(shí), 不管事務(wù)是否成功執(zhí)行, 對所有鍵的監(jiān)視都會(huì)被取消。
另外, 當(dāng)客戶端斷開連接時(shí), 該客戶端對鍵的監(jiān)視也會(huì)被取消。
使用無參數(shù)的?UNWATCH?命令可以手動(dòng)取消對所有鍵的監(jiān)視。 對于一些需要改動(dòng)多個(gè)鍵的事務(wù), 有時(shí)候程序需要同時(shí)對多個(gè)鍵進(jìn)行加鎖, 然后檢查這些鍵的當(dāng)前值是否符合程序的要求。 當(dāng)值達(dá)不到要求時(shí), 就可以使用?UNWATCH?命令來取消目前對鍵的監(jiān)視, 中途放棄這個(gè)事務(wù), 并等待事務(wù)的下次嘗試。
使用 WATCH 實(shí)現(xiàn) ZPOP
WATCH?可以用于創(chuàng)建 Redis 沒有內(nèi)置的原子操作。
舉個(gè)例子, 以下代碼實(shí)現(xiàn)了原創(chuàng)的?ZPOP?命令, 它可以原子地彈出有序集合中分值(score)最小的元素:
WATCH zset element = ZRANGE zset 0 0 MULTIZREM zset element EXEC程序只要重復(fù)執(zhí)行這段代碼, 直到?EXEC?的返回值不是空多條回復(fù)(null multi-bulk reply)即可。
Redis 腳本和事務(wù)
從定義上來說, Redis 中的腳本本身就是一種事務(wù), 所以任何在事務(wù)里可以完成的事, 在腳本里面也能完成。 并且一般來說, 使用腳本要來得更簡單,并且速度更快。
因?yàn)槟_本功能是 Redis 2.6 才引入的, 而事務(wù)功能則更早之前就存在了, 所以 Redis 才會(huì)同時(shí)存在兩種處理事務(wù)的方法。
不過我們并不打算在短時(shí)間內(nèi)就移除事務(wù)功能, 因?yàn)槭聞?wù)提供了一種即使不使用腳本, 也可以避免競爭條件的方法, 而且事務(wù)本身的實(shí)現(xiàn)并不復(fù)雜。
不過在不遠(yuǎn)的將來, 可能所有用戶都會(huì)只使用腳本來實(shí)現(xiàn)事務(wù)也說不定。 如果真的發(fā)生這種情況的話, 那么我們將廢棄并最終移除事務(wù)功能。
from:?http://redisdoc.com/topic/transaction.html
總結(jié)
以上是生活随笔為你收集整理的Redis事务(transaction)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis 集群规范
- 下一篇: Redis通信协议(protocol)