Redis的事務
- 事務簡介
- 事務基本操作
- 事務的作業流程
- 事務的注意事項
- 鎖
- 基于特定條件的事務執行
- 分布式鎖
- 死鎖
事務簡介
事務是一個資料庫必備的元素,對于redis也不例外,對于一個傳統的關系型資料庫來說,資料庫事務滿足ACID四個特性
ACID
- 原子性(Atomicity)
原子性是指事務是一個不可分割的作業單位,事務中的操作要么都發生,要么都不發生, - 一致性(Consistency)
事務前后資料的完整性必須保持一致, - 隔離性(Isolation)
事務的隔離性是多個用戶并發訪問資料庫時,資料庫為每一個用戶開啟的事務,不能被其他事務的操作資料所干擾,多個并發事務之間要相互隔離, - 持久性(Durability)
持久性是指一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響
對于redis來說,只滿足其中的:
一致性(Consistency)和隔離性(Isolation)兩個特性,其他特性是不支持的,
事務基本操作
-
開啟事務
multi
作用:設定事務的開啟位置,此指令執行后,后續的所有指令均加入到事務中 -
執行事務
exec
作用:設定事務的結束位置,同時執行事務,與multi成對出現,成對使用
注意:加入事務的命令暫時進入到任務佇列中,并沒有立即執行,只有執行exec命令才開始執行
開啟兩個redis客戶端
127.0.0.1:6379> set name maomao
OK
在另一個客戶端set name
127.0.0.1:6379> set name zhuzhu
OK
在第一個客戶端name變化了 這就是因為沒有事務造成的現象
127.0.0.1:6379> get name
"zhuzhu"
127.0.0.1:6379> multi # 開啟事務
OK
127.0.0.1:6379(TX)> set name maomao
QUEUED
127.0.0.1:6379(TX)> set age 19
QUEUED
另一臺客戶端
127.0.0.1:6379> set name zhuzhu
OK
127.0.0.1:6379(TX)> exec # 執行
1) OK
2) OK
127.0.0.1:6379> get name # 最后的值還是maomao
"maomao"
事務定義程序中發現出了問題
- 取消事務
discard
終止當前事務的定義,發生在multi之后,exec之前
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set num 100
QUEUED
127.0.0.1:6379(TX)> get age
QUEUED
127.0.0.1:6379(TX)> set nums 200
QUEUED
127.0.0.1:6379(TX)> discard # 取消事務
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
事務的作業流程

- 識別到普通指令 則直接執行
- MULTI開啟事務 創建佇列
- 識別到普通指令則將指令加入佇列(QUEUED)
- 識別到EXEC 則執行事務 回傳執行結果
- 識別到DISCARD 則銷毀佇列
事務的注意事項
定義事務的程序中,命令格式輸入錯誤怎么辦
- 語法錯誤
指命令書寫格式有誤 - 處理結果
如果定義的事務中所包含的命令存在語法錯誤,整體事務中所有命令均不會執行,包括那些語法正確的命令,
編譯型例外(代碼有問題! 命令有錯!),事務中所有的命令都不會被執行!
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name xiaotian
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> qst name pancheng
(error) ERR unknown command `qst`, with args beginning with: `name`, `pancheng`,
127.0.0.1:6379(TX)> exec # 如果事務中存在語法錯誤 全部命令都不執行
(error) EXECABORT Transaction discarded because of previous errors.
定義事務的程序中,命令執行出現錯誤怎么辦
- 運行錯誤
指命令格式正確,但是無法正確的執行,例如對list進行incr操作 - 處理結果
能夠正確運行的命令會執行,運行錯誤的命令不會被執行
運行時例外(1/0),如果事務中有存在語法性錯誤,那么執行命令的時候,其他命令是可以正常執行的,錯誤命令拋出例外!
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name xiaotian
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> set name pancheng
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> lpush name a b c
QUEUED
127.0.0.1:6379(TX)> set name zhu
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> exec# 語法性錯誤,那么執行命令的時候,其他命令是可以正常執行的
1) OK
2) "xiaotian"
3) OK
4) "pancheng"
5) (error) WRONGTYPE Operation against a key holding the wrong kind of value
6) OK
7) "zhu"
例2:
127.0.0.1:6379> set k1 'hello'
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec # 雖然第一條命令報錯了,但是依舊正常執行成功了!
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379> get k3
"v3"
注意:已經執行完畢的命令對應的資料不會自動回滾,需要程式員自己在代碼中實作回滾,
手動進行事務回滾
- 記錄操作程序中被影響的資料之前的狀態
- 單資料:string
- 多資料:hash、list、set、zset
- 設定指令恢復所有的被修改的項
- 單資料:直接set(注意周邊屬性,例如時效)
- 多資料:修改對應值或整體克隆復制
鎖
基于特定條件的事務執行
天貓雙11熱賣程序中,對已經售罄的貨物追加補貨,4個業務員都有權限進行補貨,補貨的操作可能是一系列的操作,牽扯到多個連續操作,如何保障不會重復操作?
加粗樣式
- 多個客戶端有可能同時操作同一組資料,并且該資料一旦被操作修改后,將不適用于繼續操作
- 在操作之前鎖定要操作的資料,一旦發生變化,終止當前操作
解決方案
- 對 key 添加監視鎖,在執行exec前如果key發生了變化,終止事務執行
watch key1 [key2……]
- 取消對所有 key 的監視
unwatch
127.0.0.1:6379> set name xiaotian
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> watch name
OK
127.0.0.1:6379> get name
"xiaotian"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set aa bb
QUEUED
127.0.0.1:6379(TX)> get aa
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) "bb"
這次監控兩個key 并且去另一個客戶端更改key的值
127.0.0.1:6379> watch name age
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set aa cc
QUEUED
127.0.0.1:6379(TX)> get aa
QUEUED
這里去另一個客戶端
127.0.0.1:6379> set name zhuzhu
OK
127.0.0.1:6379> get name
"zhuzhu"
結構發現事務直接為nil
127.0.0.1:6379(TX)> exec
(nil)
說明watch的key被改變 接下來的事務執行失敗
在事務里不能執行watch
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> watch name
(error) ERR WATCH inside MULTI is not allowed
watch的key不能改變 是 能否執行下一個事務的條件,和下一個事務里面執行的內容無關
- redis 應用基于狀態控制的批量任務執行
分布式鎖
天貓雙11熱賣程序中,對已經售罄的貨物追加補貨,且補貨完成,客戶購買熱情高漲,3秒內將所有商品購買完畢,本次補貨已經將庫存全部清空,如何避免最后一件商品不被多人同時購買?【超賣問題】
- 使用watch監控一個key有沒有改變已經不能解決問題,此處要監控的是具體資料
- 雖然redis是單執行緒的,但是多個客戶端對同一資料同時進行操作時,如何避免不被同時修改?
解決方案
-
使用 setnx 設定一個公共鎖
setnx lock-key value
-
利用setnx命令的回傳值特征,有值則回傳設定失敗,無值則回傳設定成功
- 對于回傳設定成功的,擁有控制權,進行下一步的具體業務操作
- 對于回傳設定失敗的,不具有控制權,排隊或等待
-
操作完畢通過del操作釋放鎖
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> setnx lock-num 1 # 添加一個鎖
(integer) 1
127.0.0.1:6379> incrby num -1
(integer) 9
在解鎖之前 其他客戶端是不能使用這個鎖的
127.0.0.1:6379> setnx lock-num 1
(integer) 0 # 回傳0 失敗
127.0.0.1:6379> del lock-num # 釋放鎖
(integer) 1
127.0.0.1:6379> get num
"9"
其他客戶端方能使用
127.0.0.1:6379> setnx lock-num 1
(integer) 1
127.0.0.1:6379> incrby num -1
(integer) 8
127.0.0.1:6379> del lock-num
(integer) 1
- 注意:上述解決方案是一種設計概念,依賴規范保障,具有風險性
死鎖
依賴分布式鎖的機制,某個用戶操作時對應客戶端宕機,且此時已經獲取到鎖,如何解決?
- 由于鎖操作由用戶控制加鎖解鎖,必定會存在加鎖后未解鎖的風險
- 需要解鎖操作不能僅依賴用戶控制,系統級別要給出對應的保底處理方案
解決方案
- 使用 expire 為鎖key添加時間限定,到時不釋放,放棄鎖
expire lock-key second
pexpire lock-key milliseconds
127.0.0.1:6379> set name xiaotian
OK
127.0.0.1:6379> setnx lock-name 1
(integer) 1
127.0.0.1:6379> expire lock-name 20 # 設定一個有效期
(integer) 1
127.0.0.1:6379> get name
"xiaotian"
127.0.0.1:6379> del lock-name
(integer) 1
127.0.0.1:6379> setnx lock-name 1
(integer) 1
127.0.0.1:6379> expire lock-name 20
(integer) 1
在有效期內其他客戶端無法獲取鎖
127.0.0.1:6379> setnx lock-name 1
(integer) 0
127.0.0.1:6379> setnx lock-name 1
(integer) 0
127.0.0.1:6379> setnx lock-name 1
(integer) 0
127.0.0.1:6379> setnx lock-name 1 # 到期之后就能獲取鎖了
(integer) 1
- 由于操作通常都是微秒或毫秒級,因此該鎖定時間不宜設定過大,具體時間需要業務測驗后確認,
- 例如:持有鎖的操作最長執行時間127ms,最短執行時間7ms,
- 測驗百萬次最長執行時間對應命令的最大耗時,測驗百萬次網路延遲平均耗時
- 鎖時間設定推薦:最大耗時120%+平均網路延遲110%
- 如果業務最大耗時<<網路平均延遲,通常為2個數量級,取其中單個耗時較長即可
在實驗一個小案例
事務
127.0.0.1:6379> set money 100 # 有100塊錢
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 監視 money 物件
OK
127.0.0.1:6379> multi # 事務正常結束,資料期間沒有發生變動,這個時候就正常執行成功!
OK
127.0.0.1:6379(TX)> DECRBY money 20 # 減少20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20 # 支出就增加20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
樂觀鎖
127.0.0.1:6379> watch money # 監視 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379> set money 1000 # 其他客戶端
127.0.0.1:6379(TX)> exec # 執行之前,另外一個執行緒,修改了我們的值,這個時候,就會導致事務執行失敗
(nil)
如果修改失敗,獲取最新的值就行了
127.0.0.1:6379> unwatch # 如果發現事務執行失敗,就先解鎖
OK
127.0.0.1:6379> watch money # 獲取最新的值,再次監視,select version
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> exec # 比對監視的值是否發生了變化,如果沒有變化,那么可以執行成功,如果變化了就執行失敗
1) (integer) 990
2) (integer) 30
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/277816.html
標籤:其他
上一篇:MySQL表的增刪改查3
下一篇:Hbase的安裝和基本使用
