Redis : redis事务
Redis的事務功能詳解
MULTI、EXEC、DISCARD和WATCH命令是Redis事務功能的基礎。
Redis事務允許在一次單獨的步驟中執行一組命令,并且可以保證如下兩個重要事項:
>Redis會將一個事務中的所有命令序列化,然后按順序執行。Redis不可能在一個Redis事務的執行過程中插入執行另一個客戶端發出的請求。這樣便能保證Redis將這些命令作為一個單獨的隔離操作執行。 > 在一個Redis事務中,Redis要么執行其中的所有命令,要么什么都不執行。因此,Redis事務能夠保證原子性。
EXEC命令會觸發執行事務中的所有命令。因此,當某個客戶端正在執行一次事務時,如果它在調用EXEC命令之前就從Redis服務端斷開連接,那么就不會執行事務中的任何操作;相反,如果它在調用EXEC命令之后才從Redis服務端斷開連接,那么就會執行事務中的所有操作。(有出錯也不會停止了,后面解釋)
當Redis使用只增文件(AOF:Append-only File)時,Redis能夠確保使用一個單獨的write(2)系統調用,這樣便能將事務寫入磁盤。然而,如果Redis服務器宕機,或者系統管理員以某種方式停止Redis服務進程的運行,那么Redis很有可能只執行了事務中的一部分操作。Redis將會在重新啟動時檢查上述狀態,然后退出運行,并且輸出報錯信息。使用redis-check-aof工具可以修復上述的只增文件,這個工具將會從上述文件中刪除執行不完全的事務,這樣Redis服務器才能再次啟動。
從2.2版本開始,除了上述兩項保證之外,Redis還能夠以樂觀鎖的形式提供更多的保證,這種形式非常類似于“檢查再設置”(CAS:Check And Set)操作。本文稍后會對Redis的樂觀鎖進行描述。
一、相關命令
1. MULTI
該命令用來開啟事務,它總是返回ok結果,當其執行之后,客戶端可以繼續發送任意條數量的指令,這些指令不會立即被執行,而是被放到了隊列中,直到EXEC被調用之后,所有命令才會被序列化執行。
2. EXEC
在一個事務中執行所有先前放入隊列的命令,然后恢復正常的連接狀態。
當使用WATCH命令時,只有當受監控的鍵沒有被修改時,EXEC命令才會執行事務中的命令,這種方式利用了檢查再設置(CAS)的機制。
這個命令的運行格式如下所示:
EXEC
這個命令的返回值是一個數組,其中的每個元素分別是原子化事務中的每個命令的返回值。當使用WATCH命令時,如果事務執行中止,那么EXEC命令就會返回一個Null值。
MULTI開啟之后,因為某些原因沒有成功執行EXEC,那么事務中所有的命令都不會被執行的。
?
3. DISCARD
清除所有先前在一個事務中放入隊列的命令,然后恢復正常的連接狀態。
如果使用了WATCH命令,那么DISCARD命令就會將當前連接監控的所有鍵取消監控。
這個命令的運行格式如下所示:
DISCARD
這個命令的返回值是一個簡單的字符串,總是OK。
4. WATCH
當某個事務需要按條件執行時,就要使用這個命令將給定的鍵設置為受監控的。
這個命令的運行格式如下所示:
WATCH key [key ...]
這個命令的返回值是一個簡單的字符串,總是OK。
對于每個鍵來說,時間復雜度總是O(1)。
?
NOTE:
A、WATCH使得EXEC命令需要有條件的執行,也就是事務只能在所有被監視的鍵沒有被修改的前提下才能執行。另外,在EXEC被執行之后,所有的WATCH都會被取消。
?
5. UNWATCH
清除所有先前為一個事務監控的鍵。
如果你調用了EXEC或DISCARD命令,那么就不需要手動調用UNWATCH命令。
這個命令的運行格式如下所示:
UNWATCH
這個命令的返回值是一個簡單的字符串,總是OK。
時間復雜度總是O(1)。
二、使用方法及事務內部的錯誤示范
使用MULTI命令便可以進入一個Redis事務。這個命令的返回值總是OK。此時,用戶可以發出多個Redis命令。Redis會將這些命令放入隊列,而不是執行這些命令。一旦調用EXEC命令,那么Redis就會執行事務中的所有命令。
Redis原生使用(Redis-cli):
127.0.0.1:6379> multi?? ??// 事務開始的動作標志下面即為入隊
OK
127.0.0.1:6379> set book-name "Thinking in Java"
QUEUED
127.0.0.1:6379> get book-name
QUEUED
127.0.0.1:6379> sadd tag "java""Programming""Thinking"
QUEUED
127.0.0.1:6379> smembers tag
QUEUED
127.0.0.1:6379> exec?? ??// 執行事務
1) OK
2) "Thinking in Java"
3) (integer) 3
4) 1) "Thinking"
?? 2) "Programming"
?? 3) "java"
127.0.0.1:6379> discard? // 事務已執行完畢 已經自動取消
(error) ERR DISCARD without MULTI
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set book-name "Patterns in Java"
QUEUED
127.0.0.1:6379> get book-name
QUEUED
127.0.0.1:6379> sadd tag "Java""Thinking""Programming"
QUEUED
127.0.0.1:6379> smembers tag
QUEUED
127.0.0.1:6379> discard? // 事務未執行 可以刷新隊列指令狀態 取消執行
OK
127.0.0.1:6379> exec? ???// 事務已經被取消不能再執行
(error) ERR EXEC without MULTI
四、為什么Redis不支持回滾?
如果你具備關系型數據庫的知識背景,你就會發現一個事實:在事務運行期間,雖然Redis命令可能會執行失敗,但是Redis仍然會執行事務中余下的其他命令,而不會執行回滾操作,你可能會覺得這種行為很奇怪。
然而,這種行為也有其合理之處:
只有當被調用的Redis命令有語法錯誤時,這條命令才會執行失敗(在將這個命令放入事務隊列期間,Redis能夠發現此類問題),或者對某個鍵執行不符合其數據類型的操作:實際上,這就意味著只有程序錯誤才會導致Redis命令執行失敗,這種錯誤很有可能在程序開發期間發現,一般很少在生產環境發現。?(語法錯誤的意思吧)
?????? Redis已經在系統內部進行功能簡化,這樣可以確保更快的運行速度,因為Redis不需要事務回滾的能力。
對于Redis事務的這種行為,有一個普遍的反對觀點,那就是程序有可能會有缺陷(bug)。但是,你應當注意到:事務回滾并不能解決任何程序錯誤。例如,如果某個查詢會將一個鍵的值遞增2,而不是1,或者遞增錯誤的鍵,那么事務回滾機制是沒有辦法解決這些程序問題的。請注意,沒有人能解決程序員自己的錯誤,這種錯誤可能會導致Redis命令執行失敗。正因為這些程序錯誤不大可能會進入生產環境,所以我們在開發Redis時選用更加簡單和快速的方法,沒有實現錯誤回滾的功能。
?
鑒于沒有任何機制能避免程序員自己造成的錯誤,并且這類錯誤通常不會在生產環境中出現,所以 Redis 選擇了更簡單、更快速的無回滾方式來處理事務。
五、丟棄命令隊列
DISCARD命令可以用來中止事務運行。在這種情況下,不會執行事務中的任何命令,并且會將Redis連接恢復為正常狀態。示例如下所示:
?
六、通過CAS操作實現樂觀鎖
1、樂觀鎖實現
舉個例子,假設我們需要原子性為某個鍵加1操作(假設INCR不存在),那么應該是這樣的執行語句:
SET mykey 1
val = GET mykey
val = val + 1
SET mykey ${val}
?
單個客戶端訪問操作沒有任何問題,如果是多個客戶端同時訪問mykey,就會產生資源共享訪問問題,比如:現在有個兩個客戶端訪問同一個鍵mykey,那么mykey的可能是2,但是我們期望的值應該是3才對,這個類似于高并發下的sync鎖機制,所以我們需要使用WATCH來監控被共享的鍵mykey,如下:
WATCH mykey(可監控多個鍵)
val = GET mykey
val = val + 1
MULTI
SET mykey ${val}
EXEC
?
NOTE:
雖然大多情況下,多個客戶端訪問操作同一個鍵的情況很少或沒有,但是不能排除這個特殊情況,所以建議在有可能產生鍵共享的指令中使用WATCH(有點類似JAVA中的synchronzied)在EXEC執行前對其監管。
?
2、Redis不支持回滾(Roll Back)
Redis的事務不支持回滾,這點不同于關系數據庫中的事務,所以它的內部保持了簡單且快速的特點。另外,Redis不支持回滾是這樣考慮的:Redis事務中命令之所以會失敗,是由于錯誤的編程所造成,通過事務回滾是不能回避這個根本問題。
?
NOTE:
Redis事務中命令執行失敗,仍會繼續執行后面的執行,在沒有特殊干預前提下,直到執行完隊列中所有指令為止。
?
3、使用事務可能遇到的問題
A、事務在執行?EXEC?之前,入隊的命令可能會出錯,舉個例子:命令可能會產生語法錯誤(參數數量錯誤,參數名錯誤等),或者其他更嚴重的錯誤,比如內存不足(如果服務器使用maxmemory 設置了最大內存限制的話)。
?
B、事務在執行?EXEC?之前,舉個例子:事務中的命令可能處理了錯誤類型的鍵,比如將列表命令用在了字符串鍵上面等。
?
對于發生在?EXEC?執行之前的錯誤,客戶端以前的做法是檢查命令入隊所得的返回值:如果命令入隊時返回QUEUED ,那么入隊成功;否則,就是入隊失敗。如果有命令在入隊時失敗,那么大部分客戶端都會停止并取消這個事務。?
從 Redis 2.6.5 開始,服務器會對命令入隊失敗的情況進行記錄,并在客戶端調用?EXEC?命令時,拒絕執行并自動放棄這個事務。
在 Redis 2.6.5 以前, Redis 只執行事務中那些入隊成功的命令,而忽略那些入隊失敗的命令。
而新的處理方式則使得在管道技術中包含事務變得簡單,因為發送事務和讀取事務的回復都只需要和服務器進行一次通訊即可。
?至于那些在?EXEC?命令執行之后所產生的錯誤,并沒有對它們進行特別處理:即使事務中有某個/某些命令在執行時產生了錯誤, 事務中的其他命令仍然會繼續執行。
七、WATCH命令詳解
那么WATCH命令實際做了些什么呢?
這個命令會使得EXEC命令在滿足某些條件時才會運行事務:
我們要求Redis只有在所有受監控的鍵都沒有被修改時,才會執行事務。(但是,相同的客戶端可能會在事務內部修改這些鍵,此時這個事務不會中止運行。內部修改的沒關系)否則,Redis根本就不會進入事務。(注意,如果你使用WATCH命令監控一個易失性的鍵,然后在你監控這個鍵之后,Redis再使這個鍵過期,那么EXEC命令仍然可以正常工作。)
WATCH命令可以被調用多次。簡單說來,所有的WATCH命令都會在被調用之時立刻對相應的鍵進行監控,直到EXEC命令被調用之時為止。你可以在單條的WATCH命令之中,使用任意數量的鍵作為命令參數。
當調用EXEC命令時,所有的鍵都會變為未受監控的狀態,Redis不會管事務是否被中止。當一個客戶單連接被關閉時,所有的鍵也都會變為未受監控的狀態。(就是調用EXEC前,鍵都是受到WATCH監控,調用后就自動釋放監控了)。
你還可以使用UNWATCH命令(不需要任何參數),這樣便能清除所有的受監控鍵。當我們對某些鍵施加樂觀鎖之后,這個命令有時會非常有用。因為,我們可能需要運行一個用來修改這些鍵的事務,但是在讀取這些鍵的當前內容之后,我們可能不打算繼續進行操作,此時便可以使用UNWATCH命令,清除所有受監控的鍵。在運行UNWATCH命令之后,Redis連接便可以再次自由地用于運行新事務。
如何使用WATCH命令實現ZPOP操作呢?
本文將通過一個示例,說明如何使用WATCH命令創建一個新的原子化操作(Redis并不原生支持這個原子化操作),此處會以實現ZPOP操作為例。這個命令會以一種原子化的方式,從一個有序集合中彈出分數最低的元素。以下源碼是最簡單的實現方式:
WATCH zset
element = ZRANGEzset 0 0
MULTI
ZREM zset element
EXEC
如果偽碼中的EXEC命令執行失敗(例如,返回Null值),那么我們只需要重復運行這個操作即可。
八、Redis腳本和事務
根據定義,Redis腳本也是事務型的。因此,你可以通過Redis事務實現的功能,同樣也可以通過Redis腳本來實現,而且通常腳本更簡單、更快速。
由于Redis從2.6版本才開始引入腳本特性,而事務特性是很久以前就已經存在的,所以目前的版本才有兩個看起來重復的特性。但是,我們不太可能在短時間內移除對事務特性的支持。因為,即使不用求助于Redis腳本,用戶仍然能夠規避競爭狀態,這從語義上來看是適宜的。還有另一個更重要的原因,Redis事務特性的實現復雜度是最小的。
但是,在相當長的一段時間之內,我們不大可能看到整個用戶群體都只使用Redis腳本。如果發生這種情況,那么我們可能會廢棄,甚至最終移除Redis事務。
?????? 腳本將在下一章節和管道一起描述。
總結
以上是生活随笔為你收集整理的Redis : redis事务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis:master/slave、s
- 下一篇: linux cmake编译源码,linu