Redis實戰 - 5事務:multi、exec和watch


介紹

  1. redis的目標的是: 簡潔,高效,由於事務本身就是一個很復雜的東西,所有我們不能把事務做的太復雜。
  • DISCARD 取消事務,放棄執行事務塊內的所有命令。

  • EXEC 執行所有事務塊內的命令。

  • MULTI 標記一個事務塊的開始。

  • UNWATCH 取消 WATCH 命令對所有 key 的監視。

  • WATCH key [key ...] 監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷。

    MULTI 和 EXEC

  127.0.0.1:6379> multi 
OK
127.0.0.1:6379> lpush fruits orange
QUEUED
127.0.0.1:6379> lpush fruits nut
QUEUED
127.0.0.1:6379> lpush fruits apple
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 2
3) (integer) 3

我們發現命令是一起執行的,如果說我的某一條命令執行失敗,會回滾嗎?
答案是不會回滾。看看下面的原子性。

原子性

事務的原子性是指要么事務全部成功,要么全部失敗,那么 Redis 事務執行是原子性的么? 下面我們來看一個特別的例子。

> multi 
OK 
> set books iamastring      # 執行成功
QUEUED 
> incr books             # 執行失敗
QUEUED 
> set poorman iamdesperate   # 執行成功
QUEUED 
> exec 
1) OK 
2) (error) ERR value is not an integer or out of range 
3) OK 
> get books 
"iamastring" 
>  get poorman 
"iamdesperate 

  上面的例子是事務執行到中間遇到失敗了,因為我們不能對一個字符串進行數學運算,事務在遇到指令執行失敗后,后面的指令還繼續執行,所以 poorman 的值能繼續得到設置。
  到這里,你應該明白 Redis 的事務根本不能算「原子性」,而僅僅是滿足了事務的「隔離性」,隔離性中的串行化——當前執行的事務有着不被其它事務打斷的權利。

Watch

我在執行lpush的時候,lpush被其他人改變了。

需求:在寫multi的時候,不可以有其他的命令更改 “隊列”中的集合。

出現並發問題,因為有多個客戶端會並發進行操作。我們可以通過 Redis 的分布式鎖來避免沖突,這是一個很好的解決方案。分布式鎖是一種悲觀鎖,那是不是可以使用樂觀鎖的方式來解決沖突呢?
Redis 提供了這種 watch 的機制,它就是一種樂觀鎖。有了 watch 我們又多了一種可以用來解決並發修改的方法。 watch 的使用方式如下:

127.0.0.1:6379> watch msg
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set msg "hello wolrd"
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get msg
"12345"
127.0.0.1:6379> 

注意事項
Redis 禁止在 multi 和 exec 之間執行 watch 指令,而必須在 multi 之前做好盯住關鍵變量,否則會出錯。

.net操練,在StackExchange.Redis又該怎么做?

更復雜的事實是StackExchange.Redis使用的是多路復用器的方式。

我們不能只讓並發調用方發布 WATCH / UNWATCH / MULTI / EXEC / DISCARD:這應該是混合在一起的。所以一個額外的抽象被給出:另外會讓使事情更簡單准確:約束。約束是預定義測試包括 WATCH 某種類型的測試並對結果進行檢查。如果所有的約束都通過了,那么要么是以 MULTI / EXEC 發布(從事務開始,到執行整個事務塊);要么是以 UNWATCH 發布(取消 WATCH 命令對所有 key 的監視)。阻止命令於其它調用方被混合在一起;所以例子可以是:

注意:從 CreateTransaction 返回的對象最后都是調用異步方法來執行命令(Execute方法最終也是調用ExecuteAsync,具體可以看源碼):由於不知道每個操作的結果,除非在 Execute 或 ExecuteAsync 操作完成后。如果操作沒有被執行,所有的Task 將被標記為取消,否則在命令執行后你可以獲取每個正常的結果。

       static void Transaction()
        {
            using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379"))
            {
                IDatabase db = redis.GetDatabase();
                ITransaction tran = db.CreateTransaction();
                //為該事務添加先決條件
                //強制指定指定的哈希字段不能存在。
                 tran.AddCondition(Condition.HashNotExists("transactionDemo", "UniqueID"));
                tran.HashSetAsync("transactionDemo", "UniqueID","Unnn");
                bool committed = tran.Execute();
                Console.WriteLine(committed); //第1次執行,true,因為不存在,后面執行false,因為存在
            }
        }

通過 When 的內置操作

還應該注意的是,Redis已經為我們預料到了許多常見的場景(特別是:key/hash的存在,就像上面一樣),還有單操作(single-operation)原子命令的存在。 通過 When 來訪問,所以前面的示例也可以這樣來實現:

var newId = CreateNewId();
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);

注意:When.NotExists 會使用命令 HSETNX 而不會使用 HSET
HSETNX:如果字段已經存在於哈希表中,操作無效。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM