和眾多其它資料庫一樣,redis作為nosql資料庫也同樣提供了事務機制。在redis中,multi/exec/discard/watch 這四個命令是我們實現事務的基石。相信對有關係型資料庫開發經驗的開發者而言這一概念並不陌生,即便如此,我們還是會簡要的列出redis中事務的實現特徵:
在事務中的所有命令都將會被序列化的順序執行,事務執行期間,redis不會再為其它客戶端的請求提供任何服務,從而保證了事物中的所有命令被原子的執行。
和關係型資料庫中的事務相比,在redis事務中如果有某一條命令a執行失敗,其後的命令仍然會被繼續執行(只有命令a執行失敗,其他命令均執行成功,這也是redis事務控制中應該急需解決的問題)。
我們可以通過multi命令開啟乙個事務,有關係型資料庫開發經驗的人可以將其理解為」begin transaction」語句。在該語句之後執行的命令都將被視為事務之內的操作,最後我們可以通過執行exec/discard命令來提交/回滾該事務內的所有操作。這兩個redis命令可被視為等同於關係型資料庫中的commit/rollback語句。
在事務開啟之前,如果客戶端與伺服器之間出現通訊故障並導致網路斷開,其後所有待執行的語句都將不會被伺服器執行。然而如果網路中斷事件是發生在客戶端執行exec命令之後,那麼該事務中的所有命令都會被伺服器執行。
事務正常執行
#在當前連線上啟動乙個新的事務。
redis 127.0
.0.1:6379> multi
ok#執行事務中的第一條命令,從該命令的返回結果可以看出,該命令並沒有立即執行,而是存於事務的命令佇列。
redis 127.0
.0.1:6379> incr t1
queued
#又執行乙個新的命令,從結果可以看出,該命令也被存於事務的命令佇列。
redis 127.0
.0.1:6379> incr t2
queued
#執行事務命令佇列中的所有命令,從結果可以看出,佇列中命令的結果得到返回。
redis 127.0
.0.1:6379> exec
1) (integer) 1
2) (integer) 1
事務中存在失敗的命令
#開啟乙個新的事務。
redis 127.0
.0.1:6379> multi
ok#設定鍵a的值為string型別的3。
redis 127.0
.0.1:6379> seta3
queued
#從鍵a所關聯的值的頭部彈出元素,由於該值是字串型別,而lpop命令僅能用於list型別,因此在執行exec命令時,該命令將會失敗。
redis 127.0
.0.1:6379> lpop a
queued
#再次設定鍵a的值為字串4。
redis 127.0
.0.1:6379> seta4
queued
#獲取鍵a的值,以便確認該值是否被事務中的第二個set命令設定成功。
redis 127.0
.0.1:6379> get
aqueued
#從結果中可以看出,事務中的第二條命令lpop執行失敗,而其後的set和get命令均執行成功,這一點是redis的事務與關係型資料庫中的事務之間最為重要的差別。
redis 127.0
.0.1:6379> exec
1) ok
2) (error) err operation against a key holding the wrong kind of
value
3) ok
4) "4"
回滾事務
#為鍵t2設定乙個事務執行前的值。
redis 127.0
.0.1:6379> set t2 tt
ok#開啟乙個事務。
redis 127.0
.0.1:6379> multi
ok#在事務內為該鍵設定乙個新值。
redis 127.0
.0.1:6379> set t2 ttnew
queued
#放棄事務。
redis 127.0
.0.1:6379> discard
ok#檢視鍵t2的值,從結果中可以看出該鍵的值仍為事務開始之前的值。
redis 127.0
.0.1:6379> get t2
"tt"
watch/unwatch命令和基於cas的樂觀鎖
在redis的事務中,watch命令可用於提供cas(check-and-set)功能。假設我們通過watch命令在事務執行之前監控了多個keys,倘若在watch之後有任何key的值發生了變化,exec命令執行的事務都將被放棄,同時返回null multi-bulk應答以通知呼叫者事務執行失敗。例如,我們再次假設redis中並未提供incr命令來完成鍵值的原子性遞增,如果要實現該功能,我們只能自行編寫相應的**。其偽碼如下:
val = get mykey
val = val + 1
set mykey $val
以上**只有在單連線的情況下才可以保證執行結果是正確的,因為如果在同一時刻有多個客戶端在同時執行該段**,那麼就會出現多執行緒程式中經常出現的一種錯誤場景–競態爭用(race condition)。比如,客戶端a和b都在同一時刻讀取了mykey的原有值,假設該值為10,此後兩個客戶端又均將該值加一後set回redis伺服器,這樣就會導致mykey的結果為11,而不是我們認為的12。為了解決類似的問題,我們需要借助watch命令的幫助,見如下**:
watch mykey
val = get mykey
val = val + 1
multi
set mykey $val
exec
和此前**不同的是,新**在獲取mykey的值之前先通過watch命令監控了該鍵,此後又將set命令包圍在事務中,這樣就可以有效的保證每個連線在執行exec之前,如果當前連線獲取的mykey的值被其它連線的客戶端修改,那麼當前連線的exec命令將執行失敗。這樣呼叫者在判斷返回值後就可以獲悉val是否被重新設定成功。
使用watch和multi實現簡單的事務控制示例:
# session 1
127.0
.0.1:6379> keys *
(empty list or set)
127.0
.0.1:6379> set age 10
ok127.0
.0.1:6379> watch age
ok127.0
.0.1:6379> multi
ok127.0
.0.1:6379> set age 11
queued
127.0
.0.1:6379>
# 這個時候先不要提交執行事務(exec)
# 假設在 session 1 事務還沒有開始提交執行的時候,這時候有另外乙個使用者(session 2)對age做了修改
# session 2
127.0
.0.1:6379> set age 22
ok127.0
.0.1:6379>
# 在 session 2 使用者對age修改為22後,session 1 使用者提交執行事務
127.0
.0.1:6379> exec
(nil)
127.0
.0.1:6379> get age
"22"
# 這個時候他發現他的事務並沒有執行成功,age的值是 session 2 使用者修改後的值
# 這就是redis樂觀鎖(watch)起到的作用,這有點類似於svn的版本控制。
Redis的事務分析與簡介
事務 redis 事務可以一次執行多個命令,並且帶有以下兩個重要的保證 事務是乙個單獨的隔離操作 事務中的所有命令都會序列化 按順序地執行。事務在執行的過程中,不會被其他客戶端傳送來的命令請求所打斷。事務是乙個原子操作 事務中的命令要麼全部被執行,要麼全部都不執行。乙個事務從開始到執行會經歷以下三個...
Redis 基礎 Redis 簡介及安裝
remote dictionary server redis 是乙個由salvatore sanfilippo寫的key value儲存系統。redis是乙個開源的使用ansi c語言編寫 遵守bsd協議 支援網路 可基於記憶體亦可持久化的日誌型 key value資料庫,並提供多種語言的api。它...
Redis基礎 五 Redis事務 樂觀鎖
redis 事務可以一次執行多個命令,並且帶有以下三個重要的保證 乙個事務從開始到執行會經歷以下三個階段 我們可以理解redis事物為乙個指令碼,指令碼中如果出現編譯時錯誤 有錯 整條指令碼都不會被執行,並且並拋棄。指令碼中如果出現執行時錯誤 1 0等邏輯錯誤 除了該條指令其他的語句依然會被執行。1...