【譯】StackExchange.Redis 中文文件(五)事務
事務
redis 的事務與SQL資料庫中的事務不同。 完整的文件在這裡,但換句話說:
redis 的事務由位於 MULTI
和 EXEC
(或 DISCARD
,用於回滾)之間的命令塊組成。一旦遇到 MULTI
,該連線上的命令將不會執行:它們會排隊(並且呼叫者將獲得對每個命令的回覆 QUEUED
)。 當遇到 EXEC
時,一次性提交。如果看到的是 DISCARD
而不是 EXEC
,則一切都將被丟棄。由於事務內的命令已排隊,因此你無法在事務內做決定。例如,在 SQL 資料庫中,你可以執行以下操作(虛擬碼-僅用於說明):
// assign a new unique id only if they don't already // have one, in a transaction to ensure no thread-races var newId = CreateNewUniqueID(); // optimistic using(var tran = conn.BeginTran()) { var cust = GetCustomer(conn, custId, tran); var uniqueId = cust.UniqueID; if(uniqueId == null) { cust.UniqueId = newId; SaveCustomer(conn, cust, tran); } tran.Complete(); }
在 redis 如何做?
這在 redis 事務中根本是不可能的:一旦事務開啟,你就無法獲取資料:你的操作已排隊。幸運的是,還有另外兩個命令可以幫助我們:WATCH
和UNWATCH
。
WATCH {key}
告訴 redis 我們對指定的 key 感興趣,以便進行事務操作。redis 將自動跟蹤該 key,並且任何更改都會使事務回滾: EXEC
與 DISCARD
的作用相同(呼叫方可以檢測到該錯誤並從頭開始重試)。 因此,你可以做的是:WATCH
一個 key,以通常的方式檢查該 key 中的資料,然後 MULTI
/EXEC
進行更改。如果在檢查資料時發現實際上不需要事務,則可以使用 UNWATCH
EXEC
和 DISCARD
期間,被監視的 key 也會被重置:
WATCH {custKey}
HEXISTS {custKey} "UniqueId"
(check the reply, then either:)
MULTI
HSET {custKey} "UniqueId" {newId}
EXEC
(or, if we find there was already an unique-id:)
UNWATCH
這看起來很奇怪:在一個 MULTI
/EXEC
操作內,如果其他人更改了 key(跟蹤所有其他連結對{custKey}
的更改),事務將中止。
在 StackExchange.Redis 如何做?
由於 StackExchange.Redis 使用多路複用方法,因此使情況更加複雜。我們不能簡單地讓併發呼叫者發出 WATCH
/ UNWATCH
/ MULTI
/ EXEC
/ DISCARD
:它們會混雜在一起。因此,提供了一個附加的抽象(使得事情變得更容易正確處理):* constraints*。 Constraints 基本上是預先包裝好的涉及 WATCH
的測試,某種測試以及對結果的檢查。如果所有約束都通過,則發出 MULTI
/EXEC
;否則發出 UNWATCH
。所有這些都以防止命令與其他呼叫者混合在一起的方式完成。 因此我們的示例變為:
var newId = CreateNewId();
var tran = db.CreateTransaction();
tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID"));
tran.HashSetAsync(custKey, "UniqueID", newId);
bool committed = tran.Execute();
// ^^^ if true: it was applied; if false: it was rolled back
請注意,從 CreateTransaction
返回的物件只能訪問 async 方法:因為每個操作的結果只有在 Execute
(或 ExecuteAsync
)完成後才能知道。如果操作未開始,則所有 Task
都將標記為已取消,否則,在命令執行後,你可以正常獲取每個結果。
可用的 條件 集並不廣泛,但涵蓋了最常見的情況; 如果你還有其他條件需要檢視,請與我聯絡(或者更好的方法是:提交 pull-request)。
通過 When
進行內建操作
還應注意,redis 已預見到許多常見情況(特別是:key/hash 的存在,如上所示),並且存在單操作原子命令。這些可以通過 When
引數來訪問。因此,我們前面的示例也可以寫為:
var newId = CreateNewId();
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);
(這裡,When.NotExists
導致使用 HSETNX
命令,而不是 HSET
)
Lua
你還應該記住,redis 2.6及更高版本支援Lua指令碼,這是一種多功能工具,可在伺服器上作為單個原子單元執行多項操作。由於在 Lua 指令碼中沒有其他連線得到服務,因此它的行為很像事務,但是沒有 MULTI
/EXEC
等複雜性。這也避免了呼叫者和伺服器之間的頻寬和等待時間等問題,但是代價是它在指令碼持續時間內壟斷伺服器。
在 redis 層(並假設 HSETNX
不存在)可以實現為:
EVAL "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end" 1 {custKey} {newId}
可以通過 StackExchange.Redis 實現:
var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
new RedisKey[] { custKey }, new RedisValue[] { newId });
(請注意,來自 ScriptEvaluate
和 ScriptEvaluateAsync
的響應是可變的,具體取決於你的指令碼;響應可以通過強制轉換來解釋:通常為 bool
)
原文地址:Transactions