【Redis学习】Transaction事务管理
1、相關命令:
(1)MULTI
標記一個事務塊的開始。事務塊內的多條命令會按照先后順序被放進一個隊列當中,最后由 EXEC 命令原子性(atomic)地執行。
返回值:總是返回 OK 。
redis> MULTI # 標記事務開始 OKredis> INCR user_id # 多條命令按順序入隊 QUEUEDredis> INCR user_id QUEUEDredis> INCR user_id QUEUEDredis> PING QUEUEDredis> EXEC # 執行 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG(2)EXEC
執行所有事務塊內的命令。假如某個(或某些) key 正處于 WATCH 命令的監視之下,且事務塊中有和這個(或這些) key 相關的命令,那么 EXEC 命令只在這個(或這些) key 沒有被其他命令所改動的情況下執行并生效,否則該事務被打斷(abort)。
返回值:事務塊內所有命令的返回值,按命令執行的先后順序排列。當操作被打斷時,返回空值 nil 。
(3)DISCARD
取消事務,放棄執行事務塊內的所有命令。如果正在使用 WATCH 命令監視某個(或某些) key,那么取消所有監視,等同于執行命令 UNWATCH 。
返回值:總是返回 OK 。
redis> MULTI OKredis> PING QUEUEDredis> SET greeting "hello" QUEUEDredis> DISCARD OK(4)WATCH
WATCH key [key …]
監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷。
返回值:總是返回 OK 。
redis> WATCH lock lock_times OK(5)UNWATCH
取消 WATCH 命令對所有 key 的監視。如果在執行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被執行了的話,那么就不需要再執行 UNWATCH 了。因為 EXEC 命令會執行事務,因此 WATCH 命令的效果已經產生了;而 DISCARD 命令在取消事務的同時也會取消所有對 key 的監視,因此這兩個命令執行之后,就沒有必要執行 UNWATCH 了。
返回值:總是 OK 。
redis> WATCH lock lock_times OKredis> UNWATCH OK使用實例:
# 事務被成功執行redis> MULTI OKredis> INCR user_id QUEUEDredis> INCR user_id QUEUEDredis> INCR user_id QUEUEDredis> PING QUEUEDredis> EXEC 1) (integer) 1 2) (integer) 2 3) (integer) 3 4) PONG# 監視 key ,且事務成功執行redis> WATCH lock lock_times OKredis> MULTI OKredis> SET lock "huangz" QUEUEDredis> INCR lock_times QUEUEDredis> EXEC 1) OK 2) (integer) 1# 監視 key ,且事務被打斷redis> WATCH lock lock_times OKredis> MULTI OKredis> SET lock "joe" # 就在這時,另一個客戶端修改了 lock_times 的值 QUEUEDredis> INCR lock_times QUEUEDredis> EXEC # 因為 lock_times 被修改, joe 的事務執行失敗 (nil)2、事務的執行過程:
一個事務的執行過程分四步:開始事務→命令入隊→命令執行→取消事務。
(1)開始事務
MULTI命令的執行標志著事務的開始。當客戶端處于非事務狀態下時, 所有發送給服務器端的命令都會立即被服務器執行。
Redis 的事務不可嵌套, 當客戶端已經處于事務狀態, 而客戶端又再向服務器發送 MULTI 時, 服務器只是簡單地向客戶端發送一個錯誤, 然后繼續等待其他命令的入隊。MULTI 命令的發送不會造成整個事務失敗, 也不會修改事務隊列中已有的數據。
(2)命令入隊
當客戶端進入事務狀態之后, 服務器在收到來自客戶端的命令時,不會立即執行命令, 而是將這些命令全部放進一個事務隊列里, 然后返回 QUEUED , 表示命令已入隊:
事務隊列是一個數組,每個數組項都包含三個屬性:
a.要執行的命令(cmd)
b. 命令的參數(argv)
c. 參數的個數(argc)
(3)命令執行
如果客戶端正處于事務狀態, 那么當 EXEC 命令執行時, 服務器根據客戶端所保存的事務隊列, 以先進先出(FIFO)的方式執行事務隊列中的命令:最先入隊的命令最先執行, 而最后入隊的命令最后執行。
當事務隊列中的所有命令被執行完之后,EXEC命令會將回復隊列作為自己的執行結果返回給客戶端,客戶端從事務狀態返回到非事務狀態。至此,事務執行完畢。
(4)取消事務
DISCARD 命令用于取消一個事務,它清空客戶端的整個事務隊列,然后將客戶端從事務狀態調整回非事務狀態,最后返回字符串 OK 給客戶端,說明事務已被取消。
事務命令與非事務命令:
無論在事務狀態下, 還是在非事務狀態下, Redis 命令都由同一個函數執行, 所以它們共享很多服務器的一般設置, 比如 AOF 的配置、RDB 的配置,以及內存限制,等等。
事務中的命令和普通命令在執行上還是有一點區別的,其中最重要的兩點是:
(1)非事務狀態下的命令以單個命令為單位執行,前一個命令和后一個命令的客戶端不一定是同一個;而事務狀態則是以一個事務為單位,執行事務隊列中的所有命令,除非當前事務執行完畢,否則服務器不會中斷事務,也不會執行其他客戶端的其他命令。
(2)在非事務狀態下,執行命令所得的結果會立即被返回給客戶端;而事務則是將所有命令的結果集合到回復隊列,再作為 EXEC 命令的結果返回給客戶端。
3、事務內部的錯誤
在一個事務的運行期間,可能會遇到兩種類型的命令錯誤:
(1) 一個命令可能會在被放入隊列時失敗。因此,事務有可能在調用EXEC命令之前就發生錯誤。
例如,這個命令可能會有語法錯誤(參數的數量錯誤、命令名稱錯誤,等等),或者可能會有某些臨界條件(例如:如果使用maxmemory指令,為Redis服務器配置內存限制,那么就可能會有內存溢出條件)。
(2)在調用EXEC命令之后,事務中的某個命令可能會執行失敗。
例如,我們對某個鍵執行了錯誤類型的操作(例如,對一個字符串(String)類型的鍵執行列表(List)類型的操作)。
可以使用Redis客戶端檢測第一種類型的錯誤,在調用EXEC命令之前,這些客戶端可以檢查被放入隊列的命令的返回值:如果命令的返回值是QUEUE字符串,那么就表示已經正確地將這個命令放入隊列;否則,Redis將返回一個錯誤。如果將某個命令放入隊列時發生錯誤,那么大多數客戶端將會中止事務,并且丟棄這個事務。
在Redis 2.6.5版本之前,如果發生了上述的錯誤,那么在客戶端調用了EXEC命令之后,Redis還是會運行這個出錯的事務,執行已經成功放入事務隊列的命令,而不會關心先前發生的錯誤。
然而,從Redis 2.6.5版本開始,服務器會記住事務積累命令期間發生的錯誤。然后,Redis會拒絕執行這個事務,在運行EXEC命令之后,便會返回一個錯誤消息。最后,Redis會自動丟棄這個事務。這樣便能輕松地混合使用事務和管道。在這種情況下,客戶端可以一次性地將整個事務發送至Redis服務器,稍后再一次性地讀取所有的返回值。
相反,在調用EXEC命令之后發生的事務錯誤,Redis不會進行任何特殊處理:在事務運行期間,即使某個命令運行失敗,所有其他的命令也將會繼續執行。
這種行為在協議層面上更加清晰。在以下示例中,當事務正在運行時,有一條命令將會執行失敗,即使這條命令的語法是正確的:
上述示例的EXEC命令的返回值是批量的字符串,包含兩個元素,一個是OK代碼,另一個是-ERR錯誤消息。客戶端會根據自身的程序庫,選擇一種合適的方式,將錯誤信息提供給用戶。
需要注意的是,即使某個命令執行失敗,事務隊列中的所有其他命令仍然會執行 —— Redis不會停止執行事務中的命令。
再看另一個示例,再次使用telnet通信協議,觀察命令的語法錯誤是如何盡快報告給用戶的:
這一次,由于INCR命令的語法錯誤,Redis根本就沒有將這個命令放入事務隊列。
4、為什么Redis不支持回滾
如果你具備關系型數據庫的知識背景,你就會發現一個事實:在事務運行期間,雖然Redis命令可能會執行失敗,但是Redis仍然會執行事務中余下的其他命令,而不會執行回滾操作,你可能會覺得這種行為很奇怪。
然而,這種行為也有其合理之處:
只有當被調用的Redis命令有語法錯誤時,這條命令才會執行失敗(在將這個命令放入事務隊列期間,Redis能夠發現此類問題),或者對某個鍵執行不符合其數據類型的操作:實際上,這就意味著只有程序錯誤才會導致Redis命令執行失敗,這種錯誤很有可能在程序開發期間發現,一般很少在生產環境發現。
Redis已經在系統內部進行功能簡化,這樣可以確保更快的運行速度,因為Redis不需要事務回滾的能力。
對于Redis事務的這種行為,有一個普遍的反對觀點,那就是程序有可能會有缺陷(bug)。但是,你應當注意到:事務回滾并不能解決任何程序錯誤。
例如,如果某個查詢會將一個鍵的值遞增2,而不是1,或者遞增錯誤的鍵,那么事務回滾機制是沒有辦法解決這些程序問題的。請注意,沒有人能解決程序員自己的錯誤,這種錯誤可能會導致Redis命令執行失敗。正因為這些程序錯誤不大可能會進入生產環境,所以我們在開發Redis時選用更加簡單和快速的方法,沒有實現錯誤回滾的功能。
5、通過CAS實現操作樂觀鎖
在redis事務中,WATCH命令可用于提供CAS(check-nd-set)功能。WATCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之后的事務就不會執行。假設我們通過WATCH命令在事務執行之前監控了一個key,之后如果這個key的值有任何的變化,后面EXEC命令執行的事務都將被放棄,同時返回null muti-bulk應答來通知調用者事務執行失敗。
val = GET mykey val = val + 1 SET mykey $val以上實現只有在單連接的時候能保證結果正確。如果同一時刻有多個客戶端同時執行該段代碼就會出現競態爭用(race condition)的的場景。比如,客戶端A和B都在同一時刻讀取了mykey的原有值,假設該值為10,此后兩個客戶端又均將該值加一后set回Redis服務器,這樣就會導致mykey的結果為11,而不是我們認為的12。
這時WATCH命令就派上用場了:如果我們能保證在GET獲得鍵值后該鍵值不被其他客戶端修改,直到事務執行完成后才允許其他客戶端進行修改,這樣可以有效的防止競態條件。
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC在獲取mykey的值之前,先用WATCH命令對其進行了監控,之后將修改mykey的SET命令放在了事務里面,這樣就可以有效的保證如果當前連接獲取的mykey被其他連接修改的話,當前連接的EXEC命令將會執行失敗。這樣調用者根據返回值就能判斷val是否被重置成功。
6、WATCH命令詳解
WATCH命令使得EXEC命令在滿足某些條件時才會運行事務:只有redis在所有受監控的鍵都沒有被修改的時候才會執行事務,否則,redis根本不會進入事務。
WATCH命令可以被調用多次。簡單的來說,所有的WATCH命令都會在被調用的時候立刻對自己監控的所有的鍵進行監控,直到EXEC命令被調用。
當調用EXEC命令時,所有的鍵都會變成未受監控的狀態,redis不會管事務是否被終止。當一個客戶端連接被關閉時,所有的鍵也都會變成未受監控的狀態。
你還可以使用UNWATCH命令(不需要任何參數),這樣便能清除所有的受監控鍵。當我們對某些鍵施加樂觀鎖之后,這個命令有時會非常有用。因為,我們可能需要運行一個用來修改這些鍵的事務,但是在讀取這些鍵的當前內容之后,我們可能不打算繼續進行操作,此時便可以使用UNWATCH命令,清除所有受監控的鍵。在運行UNWATCH命令之后,Redis連接便可以再次自由地用于運行新事務。
我們來看下面一個例子:
def hsetxx($key, $field, $value)WATCH $key$isFieldExists = HEXISTS $key, $fieldif $isFieldExists is 1MULTIHSET $key, $field, $valueEXECelseUNWATCHreturn $isFieldExists在代碼中會判斷要賦值的字段是否存在,如果字段不存在的話就不執行事務中的命令,但需要使用UNWATCH命令來保證下一個事務的執行不會受到影響。
在每個代表數據庫的Redis.h/RedisDb結構類型中,保存了一個 watched_keys 字典,字典的鍵是這個數據庫被監視的鍵,而字典的值則是一個鏈表,鏈表中保存了所有監視這個鍵的客戶端。
7、使用 WATCH 實現 ZPOP
WATCH 可以用于創建 Redis 沒有內置的原子操作。
舉個例子, 以下代碼實現了原創的 ZPOP 命令, 它可以原子地彈出有序集合中分值(score)最小的元素:
WATCH zset element = ZRANGE zset 0 0 MULTIZREM zset element EXEC程序只要重復執行這段代碼, 直到 EXEC 的返回值不是空多條回復(null multi-bulk reply)即可。
8、ACID性質
在傳統的關系式數據庫中,常常用 ACID性質來檢驗事務功能的安全性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
Redis 事務保證了其中的一致性和隔離性,但并不保證原子性和持久性。
8.1原子性(Atomicity)
單個 Redis 命令的執行是原子性的,但Redis 沒有在事務上增加任何維持原子性的機制,所以 Redis 事務的執行并不是原子性的。
如果一個事務隊列中的所有命令都被成功地執行,那么稱這個事務執行成功。
另一方面,如果 Redis 服務器進程在執行事務的過程中被停止 —— 比如接到 KILL 信號、宿主機器停機,等等,那么事務執行失敗。
當事務失敗時,Redis 也不會進行任何的重試或者回滾動作。
8.2一致性(Consistency)
Redis 的一致性問題可以分為三部分來討論:入隊錯誤、執行錯誤、Redis 進程被終結。
(1)入隊錯誤
在命令入隊的過程中,如果客戶端向服務器發送了錯誤的命令,比如命令的參數數量不對,等等,服務器將向客戶端返回一個出錯信息,并且將客戶端的事務狀態設為REDIS_DIRTY_EXEC 。
當客戶端執行 EXEC 命令時,Redis 會拒絕執行狀態為 REDIS_DIRTY_EXEC 的事務,并返回失敗信息。
因此,帶有不正確入隊命令的事務不會被執行,也不會影響數據庫的一致性。
(2)執行錯誤
如果命令在事務執行的過程中發生錯誤,比如說對一個不同類型的 key 執行了錯誤的操作,那么 Redis 只會將錯誤包含在事務的結果中,這不會引起事務中斷或整個失敗,不會影響已執行事務命令的結果,也不會影響后面要執行的事務命令,所以它對事務的一致性也沒有影響。
(3)Redis 進程被終結
如果 Redis 服務器進程在執行事務的過程中被其他進程終結,或者被管理員強制殺死,那么根據Redis 所使用的持久化模式,可能有以下情況出現:
內存模式:如果 Redis 沒有采取任何持久化機制,那么重啟之后的數據庫總是空白的,所以數據總是一致的。
RDB 模式:在執行事務時,Redis 不會中斷事務去執行保存 RDB 的工作,只有在事務執行之后,保存 RDB 的工作才有可能開始。所以當 RDB 模式下的 Redis 服務器進程在事務中途被殺死時,事務內執行的命令,不管成功了多少,都不會被保存到 RDB 文件里。恢復數據庫需要使用現有的 RDB 文件,而這個 RDB 文件的數據保存的是最近一次的數據庫快照,所以它的數據可能不是最新的,但只要RDB 文件本身沒有因為其他問題而出錯,那么還原后的數據庫就是一致的。
AOF 模式:因為保存 AOF 文件的工作在后臺線程進行,所以即使是在事務執行的中途,保存 AOF 文件的工作也可以繼續進行,因此,根據事務語句是否被寫入并保存到 AOF 文件,有以下兩種情況發生:
1)如果事務語句未寫入到 AOF 文件,或 AOF 未被SYNC 調用保存到磁盤,那么當進程被殺死之后,Redis 可以根據最近一次成功保存到磁盤的 AOF 文件來還原數據庫,只要 AOF 文件本身沒有因為其他問題而出錯,那么還原后的數據庫總是一致的,但其中的數據不一定是最新的。
2)如果事務的部分語句被寫入到 AOF 文件,并且 AOF 文件被成功保存,那么不完整的事務執行信息就會遺留在 AOF 文件里,當重啟 Redis 時,程序會檢測到 AOF 文件并不完整,Redis 會退出,并報告錯誤。需要使用 Redis-check-aof 工具將部分成功的事務命令移除之后,才能再次啟動服務器。還原之后的數據總是一致的,而且數據也是最新的(直到事務執行之前為止)。
8.3隔離性(Isolation)
Redis 是單進程程序,并且它保證在執行事務時,不會對事務進行中斷,事務可以運行直到執行完所有事務隊列中的命令為止。因此,Redis 的事務是總是帶有隔離性的。
8.4持久性(Durability)
因為事務不過是用隊列包裹起了一組 Redis 命令,并沒有提供任何額外的持久性功能,所以事務的持久性由 Redis 所使用的持久化模式決定:
在單純的內存模式下,事務肯定是不持久的。
在 RDB 模式下,服務器可能在事務執行之后、RDB 文件更新之前的這段時間失敗,所以 RDB 模式下的 Redis 事務也是不持久的。
在 AOF 的“總是 SYNC ”模式下,事務的每條命令在執行成功之后,都會立即調用 fsync 或 fdatasync 將事務數據寫入到 AOF 文件。但是,這種保存是由后臺線程進行的,主線程不會阻塞直到保存成功,所以從命令執行成功到數據保存到硬盤之間,還是有一段非常小的間隔,所以這種模式下的事務也是不持久的。
其他 AOF 模式也和“總是 SYNC ”模式類似,所以它們都是不持久的。
Redis 腳本和事務
從定義上來說, Redis 中的腳本本身就是一種事務, 所以任何在事務里可以完成的事, 在腳本里面也能完成。 并且一般來說, 使用腳本要來得更簡單,并且速度更快。
因為腳本功能是 Redis 2.6 才引入的, 而事務功能則更早之前就存在了, 所以 Redis 才會同時存在兩種處理事務的方法。
不過我們并不打算在短時間內就移除事務功能, 因為事務提供了一種即使不使用腳本, 也可以避免競爭條件的方法, 而且事務本身的實現并不復雜。
不過在不遠的將來, 可能所有用戶都會只使用腳本來實現事務也說不定。 如果真的發生這種情況的話, 那么我們將廢棄并最終移除事務功能。
9、Redis 腳本和事務
從定義上來說, Redis 中的腳本本身就是一種事務, 所以任何在事務里可以完成的事, 在腳本里面也能完成。 并且一般來說, 使用腳本要來得更簡單,并且速度更快。
因為腳本功能是 Redis 2.6 才引入的, 而事務功能則更早之前就存在了, 所以 Redis 才會同時存在兩種處理事務的方法。
不過我們并不打算在短時間內就移除事務功能, 因為事務提供了一種即使不使用腳本, 也可以避免競爭條件的方法, 而且事務本身的實現并不復雜。
不過在不遠的將來, 可能所有用戶都會只使用腳本來實現事務也說不定。 如果真的發生這種情況的話, 那么我們將廢棄并最終移除事務功能。
總結
以上是生活随笔為你收集整理的【Redis学习】Transaction事务管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大话设计模式—责任链模式
- 下一篇: 【Redis学习】Redis持久化