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

介紹

  1. redis的目標的是: 簡潔,高效,因爲事務自己就是一個很複雜的東西,全部咱們不能把事務作的太複雜。
  • DISCARD 取消事務,放棄執行事務塊內的全部命令。
  • EXEC 執行全部事務塊內的命令。
  • MULTI 標記一個事務塊的開始。
  • UNWATCH 取消 WATCH 命令對全部 key 的監視。
  • WATCH key [key ...] 監視一個(或多個) key ,若是在事務執行以前這個(或這些) key 被其餘命令所改動,那麼事務將被打斷。redis

    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

咱們發現命令是一塊兒執行的,若是說個人某一條命令執行失敗,會回滾嗎?
答案是不會回滾。看看下面的原子性。shell

原子性

事務的原子性是指要麼事務所有成功,要麼所有失敗,那麼 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 的事務根本不能算「原子性」,而僅僅是知足了事務的「隔離性」,隔離性中的串行化——當前執行的事務有着不被其它事務打斷的權利。app

# 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 以前作好盯住關鍵變量,不然會出錯。ui

.net操練,在StackExchange.Redis又該怎麼作?

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

咱們不能只讓併發調用方發佈 WATCH / UNWATCH / MULTI / EXEC / DISCARD:這應該是混合在一塊兒的。因此一個額外的抽象被給出:另外會讓使事情更簡單準確:約束。約束是預約義測試包括 WATCH 某種類型的測試並對結果進行檢查。若是全部的約束都經過了,那麼要麼是以 MULTI / EXEC 發佈(從事務開始,到執行整個事務塊);要麼是以 UNWATCH 發佈(取消 WATCH 命令對全部 key 的監視)。阻止命令於其它調用方被混合在一塊兒;因此例子能夠是:code

注意:從 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:若是字段已經存在於哈希表中,操做無效。

相關文章
相關標籤/搜索