本文主要是討論redis的事務(wù)和傳統(tǒng)事務(wù)(mysql)在設(shè)計(jì)上的區(qū)別和背后的設(shè)計(jì)決策的借鑒意義
可能大多數(shù)人都會(huì)覺(jué)得這個(gè)不是很正常,兩個(gè)不一樣的數(shù)據(jù)庫(kù)子姜,有各自的優(yōu)勢(shì)和不同的應(yīng)用場(chǎng)景趴腋,事務(wù)處理上有點(diǎn)區(qū)別有什么奇怪的啤呼,這宋舷。巷怜。葛超。確實(shí)沒(méi)錯(cuò),but....了解采取這種決策方式背后的意圖和對(duì)相同實(shí)現(xiàn)方式不同場(chǎng)景下的設(shè)計(jì)決策對(duì)比的思考延塑,對(duì)我們現(xiàn)實(shí)的項(xiàng)目設(shè)計(jì)中采取哪些方案是很有幫助的绣张。
先來(lái)看看redis和mysql對(duì)于事務(wù)處理涉及到的相關(guān)命令:
當(dāng)然mysql在事務(wù)回滾上還有更多的玩法,例如:SAVEPOINT
從這里你至少會(huì)發(fā)現(xiàn):redis沒(méi)有提供和回滾有關(guān)的命令关带,或者你會(huì)覺(jué)得還能接受侥涵,至少如果在事務(wù)執(zhí)行過(guò)程中出現(xiàn)問(wèn)題了,redis整個(gè)的事務(wù)還能回滾宋雏,然而芜飘,實(shí)際并不是這樣的,redis的事務(wù)中好芭,就算其中一個(gè)執(zhí)行失敗了燃箭,后面的事務(wù)還是會(huì)繼續(xù)進(jìn)行!并不是你理想中的舍败,整個(gè)事務(wù)就失敗了招狸。
來(lái)看看redis的官方文檔是如何定義redis的原子性:
Either all of the commands or none are processed, so a Redis transaction is also atomic.
意思是:事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行邻薯,所以redis的事務(wù)也是原子性
所以裙戏,你會(huì)看到文檔中強(qiáng)調(diào):
It's important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.
而mysql事務(wù)的原子性的定義:一個(gè)事務(wù)(transaction)中的所有操作,要么全部完成厕诡,要么全部不完成累榜,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過(guò)程中發(fā)生錯(cuò)誤,會(huì)被回滾(Rollback)到事務(wù)開(kāi)始前的狀態(tài)壹罚,就像這個(gè)事務(wù)從來(lái)沒(méi)有執(zhí)行過(guò)一樣葛作。
到這里,我們整理一下redis和mysql的事務(wù)的區(qū)別:
1猖凛,沒(méi)有提供回滾相關(guān)的命令赂蠢,無(wú)法像mysql一樣提供任意時(shí)刻任意點(diǎn)的回滾
2,事務(wù)執(zhí)行過(guò)程中有某條/某些命令執(zhí)行失敗了辨泳, 事務(wù)隊(duì)列中的其他命令仍然會(huì)繼續(xù)執(zhí)行 —— Redis 不會(huì)停止和回滾執(zhí)行事務(wù)中的命令虱岂。
所以從這兩點(diǎn)上看出,redis必須保證每條命令成功執(zhí)行菠红,否則這個(gè)事務(wù)就不符合事務(wù)的標(biāo)準(zhǔn):1第岖,無(wú)法保證原子性 2,無(wú)法保證一致性
通過(guò)redis對(duì)事務(wù)介紹的文檔中看出設(shè)計(jì)者對(duì)于redis事務(wù)設(shè)計(jì)的兩個(gè)特征:
1试溯,不考慮事務(wù)執(zhí)行命令時(shí)外界上下文邏輯
2蔑滓,基于redis單線程模型實(shí)現(xiàn)的事務(wù)處理
不考慮事務(wù)執(zhí)行命令的外界上下文邏輯
redis執(zhí)行事務(wù)的過(guò)程:MULTI 命令開(kāi)啟一個(gè)事務(wù)后, 客戶端向服務(wù)器發(fā)送任意多條命令遇绞, 這些命令不會(huì)立即被執(zhí)行烫饼, 而是被放到一個(gè)隊(duì)列中, 當(dāng) EXEC命令被調(diào)用時(shí)试读, 隊(duì)列中的命令才會(huì)被執(zhí)行杠纵。
在這里,你可以發(fā)現(xiàn)redis觸發(fā)事務(wù)的執(zhí)行時(shí)是脫離外部業(yè)務(wù)邏輯的上下文關(guān)系的钩骇,這一點(diǎn)和mysql完全不同:mysql的每個(gè)語(yǔ)句是可以充分的結(jié)合到整個(gè)業(yè)務(wù)過(guò)程中比藻,也就是事務(wù)開(kāi)啟時(shí),外界上下文的觸發(fā)的每一步操作都是同步執(zhí)行的倘屹, 會(huì)對(duì)訪問(wèn)同一資源的其他事務(wù)造成影響银亲,這也是mysql事務(wù)應(yīng)用廣泛之處,并通過(guò)多版本控制實(shí)現(xiàn)不同隔離級(jí)別狀態(tài)的任意的回滾纽匙。
而redis這種簡(jiǎn)單粗暴的事務(wù)方式的帶來(lái)的直接好處是:
1务蝠,擺脫的來(lái)自于外界的阻塞,阻塞這一點(diǎn)對(duì)于要求高性能的redis來(lái)說(shuō)是絕對(duì)不可接受的烛缔,而在mysql上馏段,你會(huì)時(shí)不時(shí)看到長(zhǎng)事務(wù)導(dǎo)致了整個(gè)系統(tǒng)的性能問(wèn)題。
2践瓷,剝離了外部不可控的因素院喜,redis事務(wù)就只是命令的集合,所以redis設(shè)計(jì)者發(fā)現(xiàn)傳統(tǒng)事務(wù)都支持的回滾(roll back)這種操作完全是多余的:
Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.Redis is internally simplified and faster because it does not need the ability to roll back.
從上面可以看到設(shè)計(jì)者的理由:
1晕翠,Redis 事務(wù)的報(bào)錯(cuò)只會(huì)因?yàn)殄e(cuò)誤的語(yǔ)法而失敗或是命令用在了錯(cuò)誤類(lèi)型的鍵上面喷舀,這也就是說(shuō),失敗的命令是由編程錯(cuò)誤造成(人為)的,而這些錯(cuò)誤應(yīng)該在開(kāi)發(fā)的過(guò)程中被發(fā)現(xiàn)硫麻,而不應(yīng)該出現(xiàn)在生產(chǎn)環(huán)境中爸邢。
好像很有道理。拿愧。甲棍。說(shuō)白了redis不想為你的人為失誤買(mǎi)單。赶掖。
2,因?yàn)椴恍枰貪L七扰,所以redis內(nèi)部更簡(jiǎn)潔和高效
每個(gè)系統(tǒng)在設(shè)計(jì)時(shí)當(dāng)然都希望更健壯奢赂,可以考慮更多的異常情況和有相應(yīng)的處理機(jī)制,但隨之而來(lái)就讓系統(tǒng)更加的復(fù)雜和性能損耗颈走,在事務(wù)處理上:事務(wù)的隔離級(jí)別越高膳灶,性能就越低,數(shù)據(jù)庫(kù)系統(tǒng)也只能在這兩個(gè)中進(jìn)行權(quán)衡和取舍立由,從這里我們也可以看出: redis選擇了性能轧钓,而放棄了可能因?yàn)橥獠咳藶閱?wèn)題而導(dǎo)致數(shù)據(jù)不一致問(wèn)題(mysql事務(wù)剛好相反)发魄。
基于redis單線程模型實(shí)現(xiàn)的事務(wù)處理
可能你會(huì)疑問(wèn)上面redis會(huì)有隔離級(jí)別而導(dǎo)致性能問(wèn)題骏啰?
redis確實(shí)沒(méi)有,因?yàn)檫@些為redis的高性能讓步值骇,redis可以說(shuō)是天然的串行化的隔離級(jí)別道盏,redis事務(wù)是保證在執(zhí)行事務(wù)中的一連串操作時(shí)不受其他客戶端的命令打斷而柑,達(dá)到串行執(zhí)行的目的。配合上 WATCH就可以實(shí)現(xiàn)和mysql的串行化隔離級(jí)別的效果
而從這一執(zhí)行效果可以看到荷逞,redis還是基于現(xiàn)有的單線程模型來(lái)實(shí)現(xiàn)事務(wù)媒咳,這更能佐證一點(diǎn):設(shè)計(jì)者為了保證高性能,寧愿放棄一些外部因素導(dǎo)致事務(wù)的數(shù)據(jù)不一致問(wèn)題种远,雖然redis可以通過(guò)多線程來(lái)實(shí)現(xiàn)來(lái)引入事務(wù)回滾并能確保事務(wù)過(guò)程中的redis服務(wù)的可用性涩澡。
總結(jié)
到這里,我們知道了坠敷,redis的事務(wù)只要正確執(zhí)行(沒(méi)有人為失誤)妙同,也是同樣滿足傳統(tǒng)事務(wù)的四大特性(ACID),也了解了redis事務(wù)的使用場(chǎng)景以及做出這些決策背后的原因,可見(jiàn):
數(shù)據(jù)庫(kù)系統(tǒng)事務(wù)設(shè)計(jì)中膝迎,保證一個(gè)事務(wù)完整執(zhí)行渐溶,原本數(shù)據(jù)一致性應(yīng)該是首要位置,因此弄抬,系統(tǒng)設(shè)計(jì)中必然要考慮到更多的異常的不可控的情況和相應(yīng)的處理機(jī)制茎辐,從而保證系統(tǒng)自身的數(shù)據(jù)安全, 而在redis中,我們看到了另外一種可能:為了保證redis的高性能而在設(shè)計(jì)決策上所做的妥協(xié)和調(diào)整拖陆, 雖然保證嚴(yán)格的一致性似乎對(duì)于嚴(yán)謹(jǐn)?shù)氖聞?wù)設(shè)計(jì)方案來(lái)說(shuō)會(huì)更讓人接受弛槐,但這對(duì)于redis來(lái)說(shuō),其實(shí)僅僅只是一個(gè)局部的功能依啰,從整個(gè)系統(tǒng)的角度上乎串,高性能和效率才是redis的初衷,從而也理解redis事務(wù)的特殊之處和其應(yīng)用場(chǎng)景速警。