簡單聊一聊Redis事務
沒錯,Redis也有事務管理,但是功能很簡單,在正式開發中也并不推薦使用。但是面試中有可能會問到,所以本文簡單談一談Redis的事務。
通過這篇文章,你會了解
- Redis為什么要提供事務?
- Redis事務基本指令和使用方法
- CAS樂觀鎖是什么?
- Redis事務為什么不支持回滾?
1. 為什么要用事務
我們知道Redis的單個命令是原子性的,比如get、set、mget、mset等指令。
原子性是指操作是不可分割的,在執行完畢之前不會被任何其它任務或事件中斷,也就不會有并發的安全性問題
在涉及到多個命令的時候,如果需要把多個命令設置為一個不可分割的處理序列,就需要用到事務了。
比如,招財和陀螺各有100元,招財給陀螺轉了10元,這時候需要在Redis中把招財的金額總數-10,同時需要把陀螺的金額總數+10。這兩個操作要么同時成功,要么同時失敗,這時候就需要事務了。
實際上,Redis連這個簡單的需求都沒辦法完美做到,至于為啥,接著往下看吧
2. 事務的用法
2.1 5個基本指令
Redis提供了以下5個基本指令,先混個眼熟就行,接下來在案例中進行實操,想記不住都難
- MULTI
- EXEC
- DISCARD
- WATCH
- UNWATCH
2.2 案例演示
案例場景:招財和陀螺各有100元,招財給陀螺轉了10元,這時候需要在Redis中把招財的金額-10,同時需要把陀螺的金額+10。
2.2.1 事務提交
我們首先為陀螺和招財初始化自己的金額;然后使用MULTI命令顯式開啟Redis事務。 該命令總是直接返回OK。此時用戶可以發送多個指令,Redis不會立刻執行這些命令,而是將這些指令依次放入當前事務的指令隊列中;EXEC被調用后,所有的命令才會被依次執行。
# 給陀螺初始化100元
127.0.0.1:6379> set tuoluo 100
OK
# 給招財初始化100元
127.0.0.1:6379> set zhaocai 100
OK
# 顯式開啟事務
127.0.0.1:6379> MULTI
OK
# 給陀螺增加10元
127.0.0.1:6379(TX)> INCRBY tuoluo 10
QUEUED
# 給招財減少10元
127.0.0.1:6379(TX)> DECRBY zhaocai 10
QUEUED
# 執行事務中的所有指令(提交事務)
127.0.0.1:6379(TX)> EXEC
1) (integer) 110
2) (integer) 90
2.2.2 嵌套事務
Redis不支持嵌套事務,多個MULTI命令和單個MULTI命令效果相同。
# 第一次開啟事務
127.0.0.1:6379> MULTI
OK
# 嘗試嵌套事務
127.0.0.1:6379(TX)> MULTI
(error) ERR MULTI calls can not be nested
# 仍然處于第一個事務當中
127.0.0.1:6379(TX)>
2.2.3 放棄事務
如果開啟事務之后,中途后悔了怎么辦?調用DISCARD可以清空事務中的指令隊列,退出事務。
127.0.0.1:6379> MULTI
OK
# 在事務中調用DISCARD指令
127.0.0.1:6379(TX)> DISCARD
OK
# 會退出當前事務
127.0.0.1:6379>
2.2.4 watch指令
假如我們在一個客戶端連接中開啟了事務,另一個客戶端連接修改了這個事務涉及的變量值,將會怎樣?
client1開啟了一個轉賬的事務,事務開始時招財和陀螺各自擁有100元,在執行EXEC指令之前,client2將陀螺的余額添加了10元,此時執行EXEC之后,陀螺最終的金額為120元,招財為90元。
很明顯,這種情況下存在數據安全問題。
為此Redis提供了WATCH的指令,該指令可以為Redis事務提供CAS樂觀鎖行為,即多個連接同時更新變量的時候,會和變量的初始值進行比較,只在這個變量的值沒有被修改的情況下才會更新成新的值。
2.2.4.1 WATCH用法
對應我們的案例,我們可以使用WATCH監聽一個或多個key,如果開啟事務之前,至少有一個被監視的key在EXEC執行之前被修改了,那么整個事務都會被取消,直接返回nil(見下面的案例)。UNWATCH是WATCH的反操作。
2.2.4.2 CAS機制
CAS(Compare And Swap)比較并替換,是多并發時常用的一種樂觀鎖技術
CAS需要三個變量信息,分別是內存位置(JAVA中的內存地址,V),舊的預期值(A)和新值(B)。CAS執行時,當且僅當V和預期值A相等時,更新V的值為新值B,否則不執行更新。
3. 事務執行出錯怎么辦
事務執行時可能遇到問題,按照發生的時機不同分為兩種:
- 執行EXEC之前
- 執行EXEC之后
3.1 執行EXEC之前發生錯誤
比如指令存在語法錯誤(參數數量不對,指令單詞拼錯)導致不能進入commands隊列,這一步主要是編譯錯誤,還未到運行時。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET tuoluo
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
這種情況下事務會執行失敗,隊列中的所有指令都不會得到執行。
3.2 執行EXEC之后發生錯誤
這種錯誤往往是類型錯誤,比如對String使用了Hash的命令,這是運行時錯誤,編譯期間不會出錯
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> SET tuoluo 100
QUEUED
127.0.0.1:6379(TX)> LPOP tuoluo
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
我們發現,SET tuoluo 100的命令居然執行成功了,也就是在發生了運行異常的情況下,錯誤的指令不會被執行,但是其他的命令不會受影響。
這種方式顯然不符合我們對原子性的定義,也就是Redis的事務無法實現原子性,無法保證數據一致。
針對這種缺陷,Redis官方也是做了說明的。
4. Redis事務為什么不支持回滾
引自Redis官方文檔。
為了方便大家理解,我翻譯一下就是:
- 你們程序員的鍋,關我們Redis屁事兒!
Redis官方認為,只有在命令語法錯誤或者類型錯誤的時候,Redis命令才會執行失敗。而且他們認為有這種錯誤的語法一般也不會進入到生產環境。而且不支持回滾可以使他們有更多時間玩兒Redis運行得更簡單快捷。
這種說法多牛!如果出問題就是程序員的問題,寫錯了還讓代碼進入生產環境,那就是罪上加罪,你永遠賴不著Redis官方。
這可能就是不推薦使用Redis事務的原因了吧,雞肋是一方面,萬一被官方打臉了呢?所以Redis事務的知識稍微了解一下就好,面試被問到能回到上來就可以了。