更新時間:2022-02-15 09:34:28 來源:動力節點 瀏覽1517次
redis 中的事務由放置在MULTI和EXEC(或DISCARD用于回滾)之間的命令塊組成。一旦MULTI 遇到 a ,該連接上的命令就不會被執行- 它們被排隊(并且調用者得到QUEUED 每個的回復)。當EXEC遇到 an 時,它們都被應用在一個單元中(即沒有其他連接在操作之間獲得時間)。如果DISCARD看到 a 而不是 a 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 事務中根本不可能:一旦事務打開,您就無法獲取數據- 您的操作已排隊。幸運的是,還有另外兩個命令可以幫助我們:WATCH和UNWATCH.
WATCH {key}告訴 Redis 我們對指定的鍵感興趣以用于事務。Redis 會自動跟蹤這個鍵,任何更改都會使我們的事務回滾 -EXEC與DISCARD(調用者可以檢測到這一點并從頭開始重試)相同。所以你可以做的是:WATCH一個鍵,以正常方式從那個鍵中檢查數據,然后MULTI/EXEC你的更改。如果,當您檢查數據時,您發現您實際上并不需要該事務,您可以使用UNWATCH忘記所有被監視的鍵??。請注意,監視的鍵也會在EXEC和期間重置DISCARD。所以在 Redis 層,這在概念上是:
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只跨越一個操作 - 但重要的是我們現在也在跟蹤{custKey}所有其他連接的更改 - 如果其他任何人更改密鑰,事務將被中止。
StackExchange.Redis 使用多路復用器方法這一事實使情況變得更加復雜。我們不能簡單地讓并發調用者發出WATCH/ UNWATCH/ MULTI/ EXEC/ DISCARD:它們都會混在一起。所以提供了一個額外的抽象——另外讓事情變得更簡單:約束。約束基本上是預先準備好的測試,包括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只能訪問異步方法 - 因為每個操作的結果要等到Execute(或ExecuteAsync)完成之后才能知道。如果未應用操作,所有Tasks 將被標記為取消 - 否則,在命令執行后,您可以正常獲取每個結果。
可用條件的集合并不廣泛,但涵蓋了最常見的場景;如果您想查看其他條件,請與我聯系(或更好:提交拉取請求)。
還需要注意的是,Redis 已經預料到了許多常見的場景(特別是:key/hash 的存在,就像上面一樣),并且存在單操作原子命令。這些是通過When參數訪問的——所以我們前面的例子也可以寫成:
var newId = CreateNewId();
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);
(這里是使用命令的When.NotExists原因,而不是)HSETNXHSET
您還應該記住,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 });
(請注意,來自ScriptEvaluateand的響應ScriptEvaluateAsync是可變的,具體取決于您的確切腳本;響應可以通過強制轉換來解釋 - 在這種情況下為 a bool)
通過上述相信大家對Redis分布式事務已經有所了解,大家如果對此比較感性趣,想了解更多相關知識,不妨來關注一下動力節點的Redis教程,里面的課程內容從淺到深,通俗易懂,適合沒有基礎的朋友學習,希望對大家能夠有所幫助。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習