DISCARD
取消事務,放棄執行事務塊內的全部命令。EXEC
執行全部事務塊內的命令。MULTI
標記一個事務塊的開始。UNWATCH
取消 WATCH 命令對全部 key 的監視。WATCH
key [key ...] 監視一個(或多個) key ,若是在事務執行以前這個(或這些) key 被其餘命令所改動,那麼事務將被打斷。redis
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
更復雜的事實是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,由於存在 } }
還應該注意的是,Redis已經爲咱們預料到了許多常見的場景(特別是:key/hash的存在,就像上面同樣),還有單操做(single-operation)原子命令的存在。 經過 When 來訪問,因此前面的示例也能夠這樣來實現:
var newId = CreateNewId(); bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);
注意:When.NotExists 會使用命令 HSETNX 而不會使用 HSET HSETNX:若是字段已經存在於哈希表中,操做無效。