Redis--事務理解

生病的毛毛蟲發表於2020-11-14

事務

  • 一個成熟的資料庫系統一般都會有事務的支援,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");  //此處已經修改,最終返回null
        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

相關文章