眾所周知,事務(wù)是指“一個(gè)完整的動(dòng)作,要么全部執(zhí)行,要么什么也沒(méi)有做”。
在聊redis事務(wù)處理之前,要先和大家介紹四個(gè)redis指令,即MULTI、EXEC、DISCARD、WATCH。這四個(gè)指令構(gòu)成了redis事務(wù)處理的基礎(chǔ)。
1.MULTI用來(lái)組裝一個(gè)事務(wù);
2.EXEC用來(lái)執(zhí)行一個(gè)事務(wù);
3.DISCARD用來(lái)取消一個(gè)事務(wù);
4.WATCH用來(lái)監(jiān)視一些key,一旦這些key在事務(wù)執(zhí)行之前被改變,則取消事務(wù)的執(zhí)行。
紙上得來(lái)終覺(jué)淺,我們來(lái)看一個(gè)MULTI和EXEC的例子:
redis> MULTI //標(biāo)記事務(wù)開始
OK
redis> INCR user_id //多條命令按順序入隊(duì)
QUEUED
redis> INCR user_id
QUEUED
redis> INCR user_id
QUEUED
redis> PING
QUEUED
redis> EXEC //執(zhí)行
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) PONG
在上面的例子中,我們看到了QUEUED的字樣,這表示我們?cè)谟肕ULTI組裝事務(wù)時(shí),每一個(gè)命令都會(huì)進(jìn)入到內(nèi)存隊(duì)列中緩存起來(lái),如果出現(xiàn)QUEUED則表示我們這個(gè)命令成功插入了緩存隊(duì)列,在將來(lái)執(zhí)行EXEC時(shí),這些被QUEUED的命令都會(huì)被組裝成一個(gè)事務(wù)來(lái)執(zhí)行。
對(duì)于事務(wù)的執(zhí)行來(lái)說(shuō),如果redis開啟了AOF持久化的話,那么一旦事務(wù)被成功執(zhí)行,事務(wù)中的命令就會(huì)通過(guò)write命令一次性寫到磁盤中去,如果在向磁盤中寫的過(guò)程中恰好出現(xiàn)斷電、硬件故障等問(wèn)題,那么就可能出現(xiàn)只有部分命令進(jìn)行了AOF持久化,這時(shí)AOF文件就會(huì)出現(xiàn)不完整的情況,這時(shí),我們可以使用redis-check-aof工具來(lái)修復(fù)這一問(wèn)題,這個(gè)工具會(huì)將AOF文件中不完整的信息移除,確保AOF文件完整可用。
有關(guān)事務(wù),大家經(jīng)常會(huì)遇到的是兩類錯(cuò)誤:
1.調(diào)用EXEC之前的錯(cuò)誤
2.調(diào)用EXEC之后的錯(cuò)誤
“調(diào)用EXEC之前的錯(cuò)誤”,有可能是由于語(yǔ)法有誤導(dǎo)致的,也可能時(shí)由于內(nèi)存不足導(dǎo)致的。只要出現(xiàn)某個(gè)命令無(wú)法成功寫入緩沖隊(duì)列的情況,redis都會(huì)進(jìn)行記錄,在客戶端調(diào)用EXEC時(shí),redis會(huì)拒絕執(zhí)行這一事務(wù)。(這時(shí)2.6.5版本之后的策略。在2.6.5之前的版本中,redis會(huì)忽略那些入隊(duì)失敗的命令,只執(zhí)行那些入隊(duì)成功的命令)。我們來(lái)看一個(gè)這樣的例子:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> haha //一個(gè)明顯錯(cuò)誤的指令
(error) ERR unknown command 'haha'
127.0.0.1:6379> ping
QUEUED
127.0.0.1:6379> exec
//redis無(wú)情的拒絕了事務(wù)的執(zhí)行,原因是“之前出現(xiàn)了錯(cuò)誤”
(error) EXECABORT Transaction discarded because of previous errors.
而對(duì)于“調(diào)用EXEC之后的錯(cuò)誤”,redis則采取了完全不同的策略,即redis不會(huì)理睬這些錯(cuò)誤,而是繼續(xù)向下執(zhí)行事務(wù)中的其他命令。這是因?yàn)?,?duì)于應(yīng)用層面的錯(cuò)誤,并不是redis自身需要考慮和處理的問(wèn)題,所以一個(gè)事務(wù)中如果某一條命令執(zhí)行失敗,并不會(huì)影響接下來(lái)的其他命令的執(zhí)行。我們也來(lái)看一個(gè)例子:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 23
QUEUED
//age不是集合,所以如下是一條明顯錯(cuò)誤的指令
127.0.0.1:6379> sadd age 15
QUEUED
127.0.0.1:6379> set age 29
QUEUED
127.0.0.1:6379> exec //執(zhí)行事務(wù)時(shí),redis不會(huì)理睬第2條指令執(zhí)行錯(cuò)誤
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get age
"29" //可以看出第3條指令被成功執(zhí)行了
好了,我們來(lái)說(shuō)說(shuō)最后一個(gè)指令“WATCH”,這是一個(gè)很好用的指令,它可以幫我們實(shí)現(xiàn)類似于“樂(lè)觀鎖”的效果,即CAS(check and set)。
WATCH本身的作用是“監(jiān)視key是否被改動(dòng)過(guò)”,而且支持同時(shí)監(jiān)視多個(gè)key,只要還沒(méi)真正觸發(fā)事務(wù),WATCH都會(huì)盡職盡責(zé)的監(jiān)視,一旦發(fā)現(xiàn)某個(gè)key被修改了,在執(zhí)行EXEC時(shí)就會(huì)返回nil,表示事務(wù)無(wú)法觸發(fā)。
127.0.0.1:6379> set age 23
OK
127.0.0.1:6379> watch age //開始監(jiān)視age
OK
127.0.0.1:6379> set age 24 //在EXEC之前,age的值被修改了
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 25
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec //觸發(fā)EXEC
(nil) //事務(wù)無(wú)法被執(zhí)行