redis對事務(wù)的支持目前還比較簡單。redis只能保證一個client發(fā)起的事務(wù)中的命令可以連續(xù)的執(zhí)行,而中間不會插入其他client的命令。 由于redis是單線程來處理所有client的請求的所以做到這點是很容易的。一般情況下redis在接受到一個client發(fā)來的命令后會立即處理并 返回處理結(jié)果,但是當一個client在一個連接中發(fā)出multi命令有,這個連接會進入一個事務(wù)上下文,該連接后續(xù)的命令并不是立即執(zhí)行,而是先放到一 個隊列中。當從此連接受到exec命令后,redis會順序的執(zhí)行隊列中的所有命令。并將所有命令的運行結(jié)果打包到一起返回給client.然后此連接就 結(jié)束事務(wù)上下文。
下面可以看一個例子:
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr b
QUEUED
redis 127.0.0.1:6379> exec
1. (integer) 1
2. (integer) 1
從這個例子我們可以看到incr a ,incr b命令發(fā)出后并沒執(zhí)行而是被放到了隊列中。調(diào)用exec后倆個命令被連續(xù)的執(zhí)行,最后返回的是兩條命令執(zhí)行后的結(jié)果。
我們可以調(diào)用discard命令來取消一個事務(wù)。接著上面例子:
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr b
QUEUED
redis 127.0.0.1:6379> discard
OK
redis 127.0.0.1:6379> get a
"1"
redis 127.0.0.1:6379> get b
"1"
可以發(fā)現(xiàn)這次incr a incr b都沒被執(zhí)行。discard命令其實就是清空事務(wù)的命令隊列并退出事務(wù)上下文。
雖說redis事務(wù)在本質(zhì)上也相當于序列化隔離級別的了。但是由于事務(wù)上下文的命令只排隊并不立即執(zhí)行,所以事務(wù)中的寫操作不能依賴事務(wù)中的讀操作結(jié)果。很可能有兩個client同時做這個操作,也就是說如果我們要實現(xiàn)一個類似incr命令的功能,先get a取出我的值,再將a++,然后set a,我們期望是加兩次a從原來的1變成3. 但是很有可能兩個client的get a,取到都是1,造成最終加兩次結(jié)果卻是2。主要問題我們沒有對共享資源a的訪問進行任何的同步。也就是說redis沒提供任何的加鎖機制來同步對a的訪問。
還好redis 2.1后添加了watch命令,可以用來實現(xiàn)樂觀鎖??磦€正確實現(xiàn)incr命令的例子,只是在前面加了watcha :
redis 127.0.0.1:6379> watch a
OK
redis 127.0.0.1:6379> get a
"1"
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set a 2
QUEUED
redis 127.0.0.1:6379> exec
1. OK
redis 127.0.0.1:6379> get a
"2"
watch 命令會監(jiān)視給定的key,當exec時候如果監(jiān)視的key從調(diào)用watch后發(fā)生過變化,則整個事務(wù)會失敗。也可以調(diào)用watch多次監(jiān)視多個key.這 樣就可以對指定的key加樂觀鎖了。注意watch的key是對整個連接有效的,事務(wù)也一樣。如果連接斷開,監(jiān)視和事務(wù)都會被自動清除。當然了 exec、discard、unwatch命令都會清除連接中的所有監(jiān)視.
redis的事務(wù)實現(xiàn)是如此簡單,當然會存在一些問題。第一個問題是redis只能保證事務(wù)的每個命令連續(xù)執(zhí)行,但是如果事務(wù)中的一個命令失敗了,并不回滾其他命令,比如使用的命令類型不匹配。
redis 127.0.0.1:6379> set a 5
OK
redis 127.0.0.1:6379> lpush b 5
(integer) 1
redis 127.0.0.1:6379> set c 5
OK
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr b
QUEUED
redis 127.0.0.1:6379> incr c
QUEUED
redis 127.0.0.1:6379> exec
1. (integer) 6
2. (error) ERR Operation against a key holding the wrong kind of value
3. (integer) 6
可以看到雖然incr b失敗了,但是其他兩個命令還是執(zhí)行了。
還有一個問題是當事務(wù)的執(zhí)行過程中,如果redis意外的掛了。很遺憾只有部分命令執(zhí)行了,后面的也就被丟棄了。當然如果我們使用的append-only file方式持久化,redis會用單個write操作寫入整個事務(wù)內(nèi)容。即是是這種方式還是有可能只部分寫入了事務(wù)到磁盤。發(fā)生部分寫入事務(wù)的情況下,redis重啟時會檢測到這種情況,然后失敗退出??梢允褂胷edis-check-aof工具進行修復(fù),修復(fù)會刪除部分寫入的事務(wù)內(nèi)容。修復(fù)完后就能夠重新啟動了