Redis事務介紹
事務表示一組動作,要麽都成功,要麽都失敗。
redis事務是一組命令的集合,事務和命令一樣都是Redis最小的執行單位,以事務的單位要麽都執行要不執行。Reids事務需要用到兩個命令MULTL和EXEC,事務開始的時候先向Reids服務器發送MULTL命令,然後一次發送需要在本次事務中需要處理的命令,最後在發送EXEC命令結束事務。
舉個例子,使用redis-cli連接redis,然後在命令行工具中輸入如下命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set url http://qifuguang.me QUEUED 127.0.0.1:6379> set title winwill2012 QUEUED 127.0.0.1:6379> set desc java QUEUED 127.0.0.1:6379> EXEC 1) OK 2) OK 3) OK 127.0.0.1:6379> 127.0.0.1:6379> get url "http://qifuguang.me" 127.0.0.1:6379> get title "winwill2012" 127.0.0.1:6379> get desc "java" 127.0.0.1:6379> |
從輸出中可以看到,當輸入MULTI命令後,服務器返回OK表示事務開始成功,然後依次輸入需要在本次事務中執行的所有命令,每次輸入一個命令服務器並不會馬上執行,而是返回”QUEUED”,這表示命令已經被服務器接受並且暫時保存起來,最後輸入EXEC命令後,本次事務中的所有命令才會被依次執行,可以看到最後服務器一次性返回了三個OK,這裏返回的結果與發送的命令是按順序一一對應的,這說明這次事務中的命令全都執行成功了。
再舉個例子,在命令行工具中輸入如下命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set a a QUEUED 127.0.0.1:6379> sett b b (error) ERR unknown command ‘sett‘ 127.0.0.1:6379> set c c QUEUED 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get a (nil) 127.0.0.1:6379> get b (nil) 127.0.0.1:6379> get c (nil) 127.0.0.1:6379> |
和前面的例子一樣,先輸入MULTI最後輸入EXEC表示中間的命令屬於一個事務,不同的是中間輸入的命令有一個錯誤(set寫成了sett),這樣因為有一個錯誤的命令導致事務中的其他命令都不執行了(通過後續的get命令可以驗證),可見事務中的所有命令式同呼吸共命運的。
如果客戶端在發送EXEC命令之前斷線了,則服務器會清空事務隊列,事務中的所有命令都不會被執行。而一旦客戶端發送了EXEC命令之後,事務中的所有命令都會被執行,即使此後客戶端斷線也沒關系,因為服務器已經保存了事務中的所有命令。
除了保證事務中的所有命令要麽全執行要麽全不執行外,Redis的事務還能保證一個事務中的命令依次執行而不會被其他命令插入。試想一個客戶端A需要執行幾條命令,同時客戶端B發送了幾條命令,如果不使用事務,則客戶端B的命令有可能會插入到客戶端A的幾條命令中,如果想避免這種情況發生,也可以使用事務。
Redis事務錯誤處理
如果一個事務中的某個命令執行出錯,Redis會怎樣處理呢?要回答這個問題,首先要搞清楚是什麽原因導致命令執行出錯:
- 語法錯誤 就像上面的例子一樣,語法錯誤表示命令不存在或者參數錯誤
這種情況需要區分Redis的版本,Redis 2.6.5之前的版本會忽略錯誤的命令,執行其他正確的命令,2.6.5之後的版本會忽略這個事務中的所有命令,都不執行,就比如上面的例子(使用的Redis版本是2.8的) - 運行錯誤 運行錯誤表示命令在執行過程中出現錯誤,比如用GET命令獲取一個散列表類型的鍵值。
這種錯誤在命令執行之前Redis是無法發現的,所以在事務裏這樣的命令會被Redis接受並執行。如果食物裏有一條命令執行錯誤,其他命令依舊會執行(包括出錯之後的命令)。比如下例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set key 1 QUEUED 127.0.0.1:6379> SADD key 2 QUEUED 127.0.0.1:6379> set key 3 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) OK 127.0.0.1:6379> get key "3" |
Redis中的事務並沒有關系型數據庫中的事務回滾(rollback)功能,因此使用者必須自己收拾剩下的爛攤子。不過由於Redis不支持事務回滾功能,這也使得Redis的事務簡潔快速。
回顧上面兩種類型的錯誤,語法錯誤完全可以在開發的時候發現並作出處理,另外如果能很好地規劃Redis數據的鍵的使用,也是不會出現命令和鍵不匹配的問題的。
WATCH命令
從上面的例子我們可以看到,事務中的命令要全部執行完之後才能獲取每個命令的結果,但是如果一個事務中的命令B依賴於他上一個命令A的結果的話該怎麽辦呢?就比如說實現類似Java中的i++的功能,先要獲取當前值,才能在當前值的基礎上做加一操作。這種場合僅僅使用上面介紹的MULTI和EXEC是不能實現的,因為MULTI和EXEC中的命令是一起執行的,並不能將其中一條命令的執行結果作為另一條命令的執行參數,所以這個時候就需要引進Redis事務家族中的另一成員:WATCH命令
換個角度思考上面說到的實現i++的方法,可以這樣實現:
- 監控i的值,保證i的值不被修改
- 獲取i的原值
- 如果過程中i的值沒有被修改,則將當前的i值+1,否則不執行
這樣就能夠避免競態條件,保證i++能夠正確執行。
WATCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令(事務中的命令是在EXEC之後才執行的,EXEC命令執行完之後被監控的鍵會自動被UNWATCH)
舉個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
127.0.0.1:6379> set mykey 1 OK 127.0.0.1:6379> WATCH mykey OK 127.0.0.1:6379> set mykey 2 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set mykey 3 QUEUED 127.0.0.1:6379> EXEC (nil) 127.0.0.1:6379> get mykey "2" 127.0.0.1:6379> |
上面的例子中,首先設置mykey的鍵值為1,然後使用WATCH命令監控mykey,隨後更改mykey的值為2,然後進入事務,事務中設置mykey的值為3,然後執行EXEC運行事務中的命令,最後使用get命令查看mykey的值,發現mykey的值還是2,也就是說事務中的命令根本沒有執行(因為WATCH監控mykey的過程中,mykey被修改了,所以隨後的事務便會被取消)。
有了WATCH命令,我們就可以自己實現i++功能了,偽代碼如下:
1 2 3 4 5 6 7 8 9 10 11 |
def incr($key): WATCH $key $value = GET $key if not $value $value = 0 $value = $value + 1
MULTI SET $key $value result = EXEC return result[0] |
因為EXEC返回的是多行字符串,使用result[0]表示返回值的第一個字符串。
註意:由於WATCH命令的作用只是當被監控的鍵被修改後取消之後的事務,並不能保證其他客戶端不修改監控的值,所以當EXEC命令執行失敗之後需要手動重新執行整個事務。
執行EXEC命令之後會取消監控使用WATCH命令監控的鍵,如果不想執行事務中的命令,也可以使用UNWATCH命令來取消監控。
Redis事務介紹