騰訊面試官:「Redis 的事務瞭解麼?它的事務機制能實現 ACID 屬性麼?」
程許遠:「撓頭,這個……我知道 lua 指令碼能實現事務…」
騰訊面試官:「好的,回去等通知吧。」
碼哥,我斬獲了很多 offer,沒想到最後敗在了 「Redis 如何實現事務?」這個問題上。
我們來一步步分析:
什麼是事務 ACID?
Redis 如何實現事務?
Redis 的事務能實現哪些屬性?
Lua 指令碼實現。
鬼吹燈之《雲南蟲谷》中的摸金校尉有句話叫「合則生,分則死」,為了尋找雮塵珠他們三人分工明確、齊心協力共進退方可成功。
事務(Transaction)是並行控制單位,一個操作序列組合而成,這些操作要麼都執行,要麼都不執行。【相關推薦:Redis視訊教學】
「是一個不可分割的工作單位」。
事務在執行時,會提供專門的屬性保證:
原子性(Atomicity):一個事務的多個操作必須完成,或者都不完成(ps:MySQL 的原子性靠什麼實現呢?歡迎留言區評論);
一致性(Consistency):事務執行結束後,資料庫的完整性約束沒有被破壞,事務執行的前後順序都是合法資料狀態。
資料庫的完整性約束包括但不限於:
隔離性(Isolation):事務內部的操作與其他事務是隔離的,並行執行的各個事務之間不能互相干擾。
講究的是不同事務之間的相互影響,嚴格的隔離性對應隔離級別中的可序列化(Serializable)。
永續性(Durability):事務一旦提交,所有的修改將永久的儲存到資料庫中,即使系統崩潰重新啟動後資料也不會丟失。
碼哥,瞭解了 ACID 的具體要求後,Redis 是如何實現事務機制呢?
MULTI、EXEC、DISCARD 和 WATCH 命令是 Redis 實現事務的的基礎。
Redis 事務的執行過程包含三個步驟:
開啟事務;
命令入隊;
執行事務或丟棄;
顯式開啟一個事務
使用者端通過 MULTI
命令顯式地表示開啟一個事務,隨後的命令將排隊快取,並不會實際執行。
命令入隊
使用者端把事務中的要執行的一系列指令傳送到伺服器端。
需要注意的是,雖然指令傳送到伺服器端,但是 Redis 範例只是把這一系列指令暫存在一個命令佇列中,並不會立刻執行。
執行事務或丟棄
使用者端向伺服器端傳送提交或者丟棄事務的命令,讓 Redis 執行第二步中傳送的具體指令或者清空佇列命令,放棄執行。
Redis 只需在呼叫 EXEC 時,即可安排佇列命令執行。
也可通過 DISCARD 丟棄第二步中儲存在佇列中的命令。
Redis 事務案例
通過線上偵錯網站執行我們的樣例程式碼:try.redis.io
正常執行
通過 MULTI
和 EXEC
執行一個事務過程:
# 開啟事務 > MULTI OK # 開始定義一些列指令 > SET 「公眾號:碼哥位元組」 "粉絲 100 萬" QUEUED > SET "order" "30" QUEUED > SET "文章數" 666 QUEUED > GET "文章數" QUEUED # 實際執行事務 > EXEC 1) OK 2) OK 3) OK 4) "666"
我們看到每個讀寫指令執行後的返回結果都是 QUEUED
,表示謝謝操作都被暫存到了命令佇列,還沒有實際執行。
當執行了 EXEC
命令,就可以看到具體每個指令的響應資料。
放棄事務
通過 MULTI
和 DISCARD
丟棄佇列命令:
# 初始化訂單數 > SET "order:mobile" 100 OK # 開啟事務 > MULTI OK # 訂單 - 1 > DECR "order:mobile" QUEUED # 丟棄丟列命令 > DISCARD OK # 資料沒有被修改 > GET "order:mobile" "100"
碼哥,Redis 的事務能保證 ACID 特性麼?
這個問題問得好,我們一起來分析下。
Redis 事務可以一次執行多個命令, 並且帶有以下三個重要的保證:
批次指令在執行 EXEC 命令之前會放入佇列暫存;
收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行;
事務執行過程中,其他使用者端提交的命令不會插入到當前命令執行的序列中。
原子性
碼哥,如果事務執行過程中發生錯誤了,原子效能保證麼?
在事務期間,可能遇到兩種命令錯誤:
EXEC
命令前,傳送的指令本身就錯誤。如下:maxmemory
指令設定記憶體限制)。EXEC
命令後,命令可能會失敗。例如,命令和操作的資料型別不匹配(對 String 型別 的 value 執行了 List 列表操作);EXEC
命令時。 Redis 範例發生了故障導致事務執行失敗。EXEC 執行前報錯
在命令入隊時,Redis 就會報錯並且記錄下這個錯誤。
此時,我們還能繼續提交命令操作。
等到執行了 EXEC
命令之後,Redis 就會拒絕執行所有提交的命令操作,返回事務失敗的結果。
這樣一來,事務中的所有命令都不會再被執行了,保證了原子性。
如下是指令入隊發生錯誤,導致事務失敗的例子:
#開啟事務 > MULTI OK #傳送事務中的第一個操作,但是Redis不支援該命令,返回報錯資訊 127.0.0.1:6379> PUT order 6 (error) ERR unknown command `PUT`, with args beginning with: `order`, `6`, #傳送事務中的第二個操作,這個操作是正確的命令,Redis把該命令入隊 > DECR b:stock QUEUED #實際執行事務,但是之前命令有錯誤,所以Redis拒絕執行 > EXEC (error) EXECABORT Transaction discarded because of previous errors.
EXEC 執行後報錯
事務操作入隊時,命令和操作的資料型別不匹配,但 Redis 範例沒有檢查出錯誤。
但是,在執行完 EXEC 命令以後,Redis 實際執行這些指令,就會報錯。
敲黑板了:Redis 雖然會對錯誤指令報錯,但是事務依然會把正確的命令執行完,這時候事務的原子性就無法保證了!
碼哥,為什麼 Redis 不支援回滾?
其實,Redis 中並沒有提供回滾機制。雖然 Redis 提供了 DISCARD 命令。
但是,這個命令只能用來主動放棄事務執行,把暫存的命令佇列清空,起不到回滾的效果。
EXEC 執行時,發生故障
如果 Redis 開啟了 AOF 紀錄檔,那麼,只會有部分的事務操作被記錄到 AOF 紀錄檔中。
我們需要使用 redis-check-aof 工具檢查 AOF 紀錄檔檔案,這個工具可以把未完成的事務操作從 AOF 檔案中去除。
這樣一來,我們使用 AOF 恢復範例後,事務操作不會再被執行,從而保證了原子性。
簡單總結:
一致性
一致性會受到錯誤命令、範例故障發生時機的影響,按照命令出錯範例故障兩個維度的發生時機,可以分三種情況分析。
EXEC 執行前,入隊報錯
事務會被放棄執行,所以可以保證一致性。
EXEC 執行後,實際執行時報錯
有錯誤的執行不會執行,正確的指令可以正常執行,一致性可以保證。
EXEC 執行時,範例故障
範例故障後會進行重新啟動,這就和資料恢復的方式有關了,我們要根據範例是否開啟了 RDB 或 AOF 來分情況討論下。
如果我們沒有開啟 RDB 或 AOF,那麼,範例故障重新啟動後,資料都沒有了,資料庫是一致的。
如果我們使用了 RDB 快照,因為 RDB 快照不會在事務執行時執行。
所以,事務命令操作的結果不會被儲存到 RDB 快照中,使用 RDB 快照進行恢復時,資料庫裡的資料也是一致的。
如果我們使用了 AOF 紀錄檔,而事務操作還沒有被記錄到 AOF 紀錄檔時,範例就發生了故障,那麼,使用 AOF 紀錄檔恢復的資料庫資料是一致的。
如果只有部分操作被記錄到了 AOF 紀錄檔,我們可以使用 redis-check-aof 清除事務中已經完成的操作,資料庫恢復後也是一致的。
隔離性
事務執行又可以分成命令入隊(EXEC 命令執行前)和命令實際執行(EXEC 命令執行後)兩個階段。
所以在並行執行的時候我們針對這兩個階段分兩種情況分析:
並行操作在 EXEC
命令前執行,隔離性需要通過 WATCH
機制保證;
並行操作在 EXEC
命令之後,隔離性可以保證。
碼哥,什麼是 WATCH 機制?
我們重點來看第一種情況:一個事務的 EXEC 命令還沒有執行時,事務的命令操作是暫存在命令佇列中的。
此時,如果有其它的並行操作,同樣的 key 被修改,需要看事務是否使用了 WATCH
機制。
WATCH 機制的作用是:在事務執行前,監控一個或多個鍵的值變化情況,當事務呼叫 EXEC 命令執行時,WATCH 機制會先檢查監控的鍵是否被其它使用者端修改了。
如果修改了,就放棄事務執行,避免事務的隔離性被破壞。
同時,使用者端可以再次執行事務,此時,如果沒有並行修改事務資料的操作了,事務就能正常執行,隔離性也得到了保證。
沒有 WATCH
如果沒有 WATCH 機制, 在 EXEC 命令執行前的並行操作對資料讀寫。
當執行 EXEC 的時候,事務內部要操作的資料已經改變,Redis 並沒有做到事務之間的隔離。
並行操作在 EXEC 之後接收執行
至於第二種情況,因為 Redis 是用單執行緒執行命令,而且,EXEC 命令執行後,Redis 會保證先把命令佇列中的所有命令執行完再執行之後的指令。
所以,在這種情況下,並行操作不會破壞事務的隔離性。
永續性
如果 Redis 沒有使用 RDB 或 AOF,那麼事務的持久化屬性肯定得不到保證。
如果 Redis 使用了 RDB 模式,那麼,在一個事務執行後,而下一次的 RDB 快照還未執行前,如果發生了範例宕機,資料丟失,這種情況下,事務修改的資料也是不能保證持久化的。
如果 Redis 採用了 AOF 模式,因為 AOF 模式的三種設定選項 no、everysec 和 always 都會存在資料丟失的情況。
所以,事務的永續性屬性也還是得不到保證。
不管 Redis 採用什麼持久化模式,事務的永續性屬性是得不到保證的。
總結
Redis 的事務機制可以保證一致性和隔離性,但是無法保證永續性。
不過,因為 Redis 本身是記憶體資料庫,永續性並不是一個必須的屬性,我們更加關注的還是原子性、一致性和隔離性這三個屬性。
原子性的情況比較複雜,當事務中使用的命令語法有誤時,原子性得不到保證,在其它情況下,事務都可以原子性執行。
更多程式設計相關知識,請存取:!!
以上就是什麼是事務的ACID?Redis事務能實現ACID嗎?的詳細內容,更多請關注TW511.COM其它相關文章!