介紹
- 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:如果字段已經存在於哈希表中,操作無效。