一篇文章帶你徹底搞懂Redis?事務(wù)
Redis 事務(wù)簡介
Redis 只是提供了簡單的事務(wù)功能。其本質(zhì)是一組命令的集合,事務(wù)支持一次執(zhí)行多個命令,在事務(wù)執(zhí)行過程中,會順序執(zhí)行隊(duì)列中的命令,其他客戶端提交的命令請求不會插入到本事務(wù)執(zhí)行命令序列中。命令的執(zhí)行過程是順序執(zhí)行的,但不能保證原子性。無法像 MySQL 那樣,有隔離級別,出了問題之后還能回滾數(shù)據(jù)等高級操作。后面會詳細(xì)分析。
Redis 事務(wù)基本指令
Redis 提供了如下幾個事務(wù)相關(guān)的基礎(chǔ)指令。
MULTI
開啟事務(wù),Redis 會將后續(xù)命令加到隊(duì)列中,而不真正執(zhí)行它們,直到后續(xù)使用EXEC
來原子化的順序執(zhí)行這些命令 EXEC
執(zhí)行所有事務(wù)塊內(nèi)的命令 DISCARD
取消事務(wù),放棄執(zhí)行事務(wù)塊內(nèi)所有的命令 WATCH
監(jiān)視一個或多個 key,若事務(wù)在執(zhí)行前,這些 key 被其他命令修改,則事務(wù)被終端,不會執(zhí)行事務(wù)中的任何命令 UNWATCH
取消 WATCH
命令對所有 keys 的監(jiān)視
一般情況下,一個簡單的 Redis 事務(wù)主要分為如下幾個部分:
執(zhí)行命令MULTI
開啟一個事務(wù)。 開啟事務(wù)之后,執(zhí)行命令的多個命令會依次被放入一個隊(duì)列,放入成功則會返回QUEUED
消息。 執(zhí)行命令EXEC
提交事務(wù),Redis 會依次執(zhí)行隊(duì)列中的命令,并依次返回所有命令的結(jié)果。(若想放棄提交事務(wù),則執(zhí)行DISCARD
)。
下圖簡單介紹了下 Redis 事務(wù)執(zhí)行的過程:
實(shí)例分析
下面我們來通過一些實(shí)際具體例子,來體會下 Redis 中的事務(wù)。前面我們也說到 Redis 的事務(wù)不是正真的事務(wù),是無法完全滿足標(biāo)準(zhǔn)事務(wù)的ACID
特性的。通過下面的例子,我們來看看,Redis 的“破產(chǎn)版”事務(wù)到底存在什么問題。
[A]正常執(zhí)行提交
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET a 1 QUEUED 127.0.0.1:6379> SET b 2 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) OK 127.0.0.1:6379> GET a "1" 127.0.0.1:6379> GET b "2"
開啟事務(wù)后,提交的命令都會加入隊(duì)列(QUEUED),執(zhí)行 EXEC 后會逐步執(zhí)行命令并返回結(jié)果。這個看起來是不是和我們平時使用 MySQL 的事務(wù)操作相似,類似 start transaction 和 commit。
[B]正常取消事務(wù)
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET a 1 QUEUED 127.0.0.1:6379> SET b 2 QUEUED 127.0.0.1:6379> DISCARD OK 127.0.0.1:6379> 127.0.0.1:6379> GET a (nil) 127.0.0.1:6379> GET b (nil)
開啟事務(wù)后,若不想繼續(xù)事務(wù),使用 DISCARD 取消,前面提交的命令并不會真正執(zhí)行,相關(guān)的 key 值不變。這個看起來也和 MySQL 的事務(wù)相似,類似 start transaction 和 rollback。
[C]WATCH 監(jiān)視 key
-- 線程 1 中執(zhí)行 127.0.0.1:6379> del a (integer) 1 127.0.0.1:6379> get a (nil) 127.0.0.1:6379> SET a 0 OK 127.0.0.1:6379> WATCH a OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET a 1 QUEUED ----------------------------------------- 線程 2 中執(zhí)行 ----------------------------------------- 127.0.0.1:6379> SET a 2 ----------------------------------------- OK 127.0.0.1:6379> EXEC (nil) 127.0.0.1:6379> GET a "2"
在開啟事務(wù)之前 WATCH 了 a 的值,隨后再開啟事務(wù)。在另一個線程中設(shè)置了 a 的值(SET a 2),然后再 EXEC 執(zhí)行事務(wù),結(jié)果為 nil,
說明事務(wù)沒有被執(zhí)行。因?yàn)?a 的值在 WATCH 之后發(fā)生了變化,所以事務(wù)被取消了。
需要注意的是,這里和開啟事務(wù)的時間點(diǎn)沒有關(guān)系,與 MULTI 和另一個線程設(shè)置 a 的值的先后沒有關(guān)系。只要是在 WATCH 之后發(fā)生了變化。無論事務(wù)是否已經(jīng)開啟,執(zhí)行事務(wù)(EXEC)的時候都會取消。
普通情況下,在執(zhí)行 EXEC 和 DISCARD 命令時,都會默認(rèn)執(zhí)行 UNWATCH。
[D]語法錯誤
127.0.0.1:6379> SET a 1 OK 127.0.0.1:6379> SET b 2 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET a 11 QUEUED 127.0.0.1:6379> SETS b 22 (error) ERR unknown command 'SETS' 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> GET a "1" 127.0.0.1:6379> GET b "2"
當(dāng) Redis 開啟一個事務(wù)后,若添加的命令中有語法錯誤,會導(dǎo)致事務(wù)提交失敗。這種情況下事務(wù)隊(duì)列中的命令都不會被執(zhí)行。如上面例子中 a 和 b 的值都是原有的值。
這類在 EXEC 之前產(chǎn)生的錯誤,如命令名稱錯誤,命令參數(shù)錯誤等,會在 EXEC 執(zhí)行之前被檢測出來,所以在發(fā)生這些錯誤的時候,事務(wù)會被取消,事務(wù)中的所有命令都不會執(zhí)行。(這種情況看起來是不是有點(diǎn)像回滾了)
[E]運(yùn)行時錯誤
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET a 1 QUEUED 127.0.0.1:6379> SET b hello QUEUED 127.0.0.1:6379> INCR b QUEUED 127.0.0.1:6379> EXEC 1) OK 2) OK 3) (error) ERR value is not an integer or out of range 127.0.0.1:6379> GET a "1" 127.0.0.1:6379> GET b "hello"
當(dāng) Redis 開啟一個事務(wù)后,添加的命令沒有出現(xiàn)前面說的語法錯誤,但是在運(yùn)行時檢測到了類型錯誤,導(dǎo)致事務(wù)最提交失?。ㄕf未完全成功可能更準(zhǔn)確點(diǎn))。此時事務(wù)并不會回滾,而是跳過錯誤命令繼續(xù)執(zhí)行。
如上面的例子,未報(bào)錯的命令值已經(jīng)修改,a 被設(shè)置成了 1,b 被設(shè)置為了 hello,但是報(bào)錯的值未被修改,即 INCR b 類型錯誤,并未執(zhí)行,b 的值也沒有被再更新。
Redis 事務(wù)與 ACID
通過上面的例子,我們已經(jīng)知道 Redis 的事務(wù)和我們通常接觸的 MySQL 等關(guān)系數(shù)據(jù)庫的事務(wù)還有有一定差異的。它不保證原子性。同時 Redis 事務(wù)也沒有事務(wù)隔離級別的概念。下面我們來具體看下 Redis 在 ACID 四個特性中,那些是滿足的,那些是不滿足的。
事務(wù)執(zhí)行可以分為命令入隊(duì)(EXEC 執(zhí)行前)和命令實(shí)際執(zhí)行(EXEC 執(zhí)行之后)兩個階段。下面我們在分析的時候,很多時候都會分這兩種情況來分析。
原子性(A)
上面的實(shí)例分析中,[A],[B],[C]三種正常的情況,我們可以很明顯的看出,是保證了原子性的。
但是一些異常情況下,是不滿足原子性的。
如 [D] 所示的情況,客戶端發(fā)送的命令有語法錯誤,在命令入隊(duì)列時 Redis 就判斷出來了。等到執(zhí)行 EXEC 命令時,Redis 就會拒絕執(zhí)行所有提交的命令,返回事務(wù)失敗的結(jié)果。此種情況下,事務(wù)中的所有命令都不會被執(zhí)行了,是保證了原子性的。 如 [E] 所示的情況,事務(wù)操作入隊(duì)時,命令和操作類型不匹配,此時 Redis 沒有檢查出錯誤(這類錯誤是運(yùn)行時錯誤)。等到執(zhí)行 EXEC 命令后,Redis 實(shí)際執(zhí)行這些命令操作時,就會報(bào)錯。需要注意的是,雖然 Redis 會對錯誤的命令報(bào)錯不執(zhí)行,但是其余正確的命令會依次執(zhí)行完。此種情況下,是無法保證原子性的。 在執(zhí)行事務(wù)的 EXEC 命令時,Redis 實(shí)例發(fā)生了故障,導(dǎo)致事務(wù)執(zhí)行失敗。此時,如果開啟了 AOF 日志,那么只會有部分事務(wù)操作被記錄到 AOF 日志中。使用redis-check-aof
工具檢測 AOF 日志文件,可以把未完成的事務(wù)操作從 AOF 文件中去除。這樣一來,使用 AOF 文件恢復(fù)實(shí)例后,事務(wù)操作不會被再執(zhí)行,從而保證了原子性。若使用的 RDB 模式,最新的 RDB 快照是在 EXEC 執(zhí)行之前生成的,使用快照恢復(fù)之后,事務(wù)中的命令也都沒有執(zhí)行,從而保證了原子性。若 Redis 沒有開啟持久化,則重啟后內(nèi)存中的數(shù)據(jù)全部丟失,也就談不上原子性了。 一致性(C)
一致性指的是事務(wù)執(zhí)行前后,數(shù)據(jù)符合數(shù)據(jù)庫的定義和要求。這點(diǎn)在 Redis 事務(wù)中是滿足的,不論是發(fā)生語法錯誤還是運(yùn)行時錯誤,錯誤的命令均不會被執(zhí)行。
EXEC 執(zhí)行之前,入隊(duì)報(bào)錯(實(shí)例分析中的語法錯誤)
事務(wù)會放棄執(zhí)行,故可以保證一致性。
EXEC 執(zhí)行之后,實(shí)際執(zhí)行時報(bào)錯(實(shí)例分析中的運(yùn)行時錯誤)
錯誤的命令不會被執(zhí)行,正確的命令被執(zhí)行,一致性可以保證。
EXEC 執(zhí)行時,實(shí)例宕機(jī)
若 Redis 沒有開啟持久化,實(shí)例宕機(jī)重啟后,數(shù)據(jù)都沒有了,數(shù)據(jù)是一致的。
若配置了 RDB 方式,RDB 快照不會在事務(wù)執(zhí)行時執(zhí)行。所以,若事務(wù)執(zhí)行到一半,實(shí)例發(fā)生了故障,此時上一次 RDB 快照中不會包含事務(wù)所做的修改,而下一次 RDB 快照還沒有執(zhí)行,實(shí)例重啟后,事務(wù)修改的數(shù)據(jù)會丟失,數(shù)據(jù)是一致的。若事務(wù)已經(jīng)完成,但新一次的 RDB 快照還沒有生成,那事務(wù)修改的數(shù)據(jù)也會丟失,數(shù)據(jù)也是一致的。
若配置了 AOF 方式。當(dāng)事務(wù)操作還沒被記錄到 AOF 日志時,實(shí)例就發(fā)生故障了,使用 AOF 日志恢復(fù)后數(shù)據(jù)是一致的。若事務(wù)中的只有部分操作被記錄到 AOF 日志,可以使用 redis-check-aof
清除事務(wù)中已經(jīng)完成的操作,數(shù)據(jù)庫恢復(fù)后數(shù)據(jù)也是一致的。
隔離性(I) 并發(fā)操作在 EXEC 執(zhí)行前,隔離性需要通過 WATCH 機(jī)制來保證 并發(fā)操作在 EXEC 命令之后,隔離性可以保證
情況 a 可以參考前面的實(shí)例分析 WATCH 命令的使用。
情況 b,由于 Redis 是單線程執(zhí)行命令,EXEC 命令執(zhí)行后,Redis 會保證先把事務(wù)隊(duì)列中的所有命令執(zhí)行完之后再執(zhí)行之后的命令。
持久性(D)
若 Redis 沒有開啟持久化,那么就是所有數(shù)據(jù)都存儲在內(nèi)存中,一旦重啟,數(shù)據(jù)就會丟失,因此此時事務(wù)的持久性是肯定無法得到保證的。
若 Redis 開啟了持久化,當(dāng)實(shí)例宕機(jī)重啟,還是會有可能丟失數(shù)據(jù),因此也并能完全保證持久性。
因此,我們可以說 Redis 事務(wù)無法一定保證持久性,僅在特殊的情況下,可以保證持久性。
關(guān)于 Redis 在開啟持久化之后,為啥還會丟失數(shù)據(jù),筆者會單獨(dú)整理一篇 Redis 持久化與主從相關(guān)的文章來介紹,此處簡單說下。
如果配置了 RDB 模式,在一個事務(wù)執(zhí)行后,下一次 RDB 快照還未執(zhí)行前,Redis 實(shí)例發(fā)生了宕機(jī),數(shù)據(jù)就會丟失、
如果配置了 AOF 模式,而 AOF 模式的三種配置選項(xiàng) no,everysec,always 也都可能會產(chǎn)生數(shù)據(jù)丟失的情況。
總結(jié)一下,Redis 事務(wù)對 ACID 的支持情況:
具備一定的原子性,但不支持回滾 滿足一致性 滿足隔離性 無法保證持久性 Redis 事務(wù)為什么不支持回滾
看一下官網(wǎng)的的說明:
What about rollbacks?
Redis does not support rollbacks of transactions since supporting rollbacks would have a significant impact on the simplicity and performance of Redis.
大部分需要事務(wù)回滾的情況是程序錯誤導(dǎo)致的,這種情況一般是開發(fā)環(huán)境,生產(chǎn)環(huán)境不應(yīng)該出現(xiàn)這種錯誤。
對于邏輯錯誤,例如應(yīng)該加 1,結(jié)果寫成了加 2,這種情況無法通過回滾來解決。
Redis 追求的是簡單高效,而傳統(tǒng)事務(wù)的實(shí)現(xiàn)相對復(fù)雜很多,這和 Redis 的設(shè)計(jì)思想是違背的。當(dāng)我們享受 Redis 的快速時,也就無法再要求它更多。
總結(jié)
本文主要介紹了 Redis 事務(wù)的基礎(chǔ)指令與執(zhí)行流程,并分析了其對傳統(tǒng) ACID 特性支持的情況,相信大家對 Redis 事務(wù)已經(jīng)有了一個簡單的了解。
通過上面的介紹,會發(fā)現(xiàn) Redis 的事務(wù)似乎有點(diǎn)雞肋,確實(shí)實(shí)際中也很少會使用。至于事務(wù)的具體實(shí)現(xiàn),筆者后續(xù)文章會結(jié)合源碼進(jìn)行分析。今天的文章就到這里,下期我們接著學(xué)。
到此這篇關(guān)于一篇文章帶你徹底搞懂Redis 事務(wù)的文章就介紹到這了,更多相關(guān)Redis 事務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis+threading實(shí)現(xiàn)多線程消息隊(duì)列的使用示例
Redis多線程消息隊(duì)列是一種使用Redis作為存儲后端的消息隊(duì)列實(shí)現(xiàn),它利用Redis的線程并發(fā)處理能力來提高消息隊(duì)列的處理效率,本文主要介紹了Redis+threading實(shí)現(xiàn)多線程消息隊(duì)列的使用示例,感興趣的可以了解一下2023-12-12緩存替換策略及應(yīng)用(以Redis、InnoDB為例)
本文以Redis、InnoDB為例給大家講解緩存替換策略及應(yīng)用,本文給大家提到五種置換策略,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-07-07Redis查看KEY的數(shù)據(jù)類型的方法和步驟
在Redis中,可以使用 TYPE 命令來查看指定key的數(shù)據(jù)類型,該命令會返回存儲在指定key中的值的數(shù)據(jù)類型,本文給大家介紹了具體的使用方法和步驟,感興趣的朋友可以參考下2024-04-04Redis高并發(fā)情況下并發(fā)扣減庫存項(xiàng)目實(shí)戰(zhàn)
本文主要介紹了Redis高并發(fā)情況下并發(fā)扣減庫存項(xiàng)目實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04python腳本實(shí)現(xiàn)Redis未授權(quán)批量提權(quán)
這篇文章主要給大家介紹了關(guān)于利用python腳本實(shí)現(xiàn)redis未授權(quán)批量提權(quán)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09Redis+Caffeine實(shí)現(xiàn)多級緩存的步驟
隨著不斷的發(fā)展,這一架構(gòu)也產(chǎn)生了改進(jìn),在一些場景下可能單純使用Redis類的遠(yuǎn)程緩存已經(jīng)不夠了,還需要進(jìn)一步配合本地緩存使用,例如Guava cache或Caffeine,從而再次提升程序的響應(yīng)速度與服務(wù)性能,這篇文章主要介紹了Redis+Caffeine實(shí)現(xiàn)多級緩存,需要的朋友可以參考下2024-01-01