事務
一個成熟的資料庫系統一般都會有事務的支援,Redis作為一個快取資料庫也不例外,Redis的事務比之關係型資料庫mysql,oracle等算比較簡單的,Redis中無需理解那麼多事務模型,可以直接使用。不過也正是因為簡單,redis的事務模型是不嚴謹的,不能像關係型資料庫那麼用Redis的事務。
Redis事務的基本用法
每個事務操作的基本過程都有如下begin, commit,和rollback,如下
begin指令標識事務的開始 commit指令標識事務的提交 rollback指令標識事務的回滾
begin( ) ;
try {
command1( ) ;
command2( ) ;
. . . .
commit( ) ;
} cache( Exception e) {
rollback( ) ;
}
Redis事務形式上基本相同指令分別是multi, exec,discard,
multi:事務開始 exec:事務執行 discard:事務丟棄
新docker- redis: 0 > multi
"OK"
新docker- redis: 0 > incr books
"QUEUED"
新docker- redis: 0 > incr books
"QUEUED"
新docker- redis: 0 > exec
1 ) "2"
2 ) "3"
以上過程如下圖所示,所有指令在exec之前都不執行,二手快取在伺服器的一個服務佇列中,伺服器一旦收到exec指令,才開始執行整個事務佇列,執行完後一次性返回所有指令結果,因為Redis 是單執行緒,不用擔心自己在執行佇列的時候被其他指令打攪,保證原子性。
原子性
事務的原子性指要麼全部成功,要麼都失敗,Redis的事務是非原子性的。如下案例。
新docker- redis: 0 > multi
"OK"
新docker- redis: 0 > set books iamastring
"QUEUED"
新docker- redis: 0 > incr books
"QUEUED"
新docker- redis: 0 > set poorman iamdesperate
"QUEUED"
新docker- redis: 0 > exec
1 ) "OK"
2 ) "ERR value is not an integer or out of range"
3 ) "OK"
如上案例是事務執行到中間有一條指令是執行失敗的,提示不能對字串進行累加。但是後面的指令還是可以成功執行,所有poorman 的值是正常設定 此處說明Redis事務不具備原子性,而僅僅是滿足了事務的“隔離性”中的序列化-----當前執行的事務有不被其他事務打斷的權利。
discard(丟棄)
Redis為事務提供discard指令,用於丟棄事務快取佇列中所有指令,在exec執行之前。
新docker- redis: 0 > get books
null
新docker- redis: 0 > multi
"OK"
新docker- redis: 0 > incr books
"QUEUED"
新docker- redis: 0 > incr books
"QUEUED"
新docker- redis: 0 > discard
"OK"
新docker- redis: 0 > exec
"ERR EXEC without MULTI"
新docker- redis: 0 > get books
null
執行discard後,佇列中所有指令都沒有執行,我們執行exec後提示無法執行空的佇列
Redis事務的優化
Redis事務在傳送每個指令到事務佇列快取時候需要經一次網路讀寫,當一個事務內部指令較多,需要的IOShi就也會線性增加,所以同城Redis客戶端在執行事務時候回結合pipeline一起使用,這樣可以將多次IO操作壓縮為單次。例如java中Redis的使用
public class MultiTest {
public static void main ( String[ ] args) throws IOException {
Jedis jedis = JedisPoolTools. getJedis ( ) ;
Pipeline pipe = jedis. pipelined ( ) ;
pipe. multi ( ) ;
pipe. incr ( "books" ) ;
pipe. incr ( "books" ) ;
Response< List< Object> > value = pipe. exec ( ) ;
pipe. close ( ) ;
System. out. println ( value. get ( ) ) ;
}
}
watch
當有一個業務場景,Redis儲存賬戶餘額,是一個整數,多個客戶端併發修改餘額,這個修改不是簡單的incrby,而是需要通過一個邏輯計算,但是Redis單指令無法完成,我們需要先取出,計算,在儲存。這樣就是去原子性,有併發問題:
方案一:我們用Redis分散式鎖來避免衝突,分散式鎖是悲觀鎖,只允許一方持有鎖,其他等待 方案二:Redis提供watch機制,是樂觀鎖,有watch我們又多了一種可以解決併發修改的方法。watch使用方法如下。
public static void main ( String[ ] args) {
Jedis jedis = JedisPoolTools. getJedis ( ) ;
jedis. watch ( "books" ) ;
jedis. set ( "books" , "100" ) ;
Pipeline pipe = jedis. pipelined ( ) ;
pipe. multi ( ) ;
pipe. incr ( "books" ) ;
pipe. incr ( "books" ) ;
try {
Response< List< Object> > value = pipe. exec ( ) ;
pipe. close ( ) ;
System. out. println ( value. get ( ) ) ;
} catch ( Exception e) {
System. out. println ( e. getMessage ( ) ) ;
}
}
watch會在事務開始之前叮囑一個或者多個關鍵變數,流程如下:
當事務執行之後,執行exec命令後要順序執行快取的指令佇列 Redis檢查關鍵變數watch之後是否被修改 如果被變數被修改過,exec指令將返回null,通知客戶端執行失敗,此時我們可以重試或者其他邏輯
注意
Redis禁止在multi 和exec之間執行watch,必須在multi之前執行watch,否則丟擲異常。
上一篇:Redis持久化-深入理解AOF,RDB