[url=原始碼學習筆記》文章列表[/url]
redis中的事務,提供了一種「[b]將多個命令打包並且一次執行[/b]」的方式;
當使用者輸入multi命令時,就開啟了客戶端redis_multi選項,客戶端從「非事務狀態」切換到「事務狀態」
[img]
之後客戶端執行的所有命令都不會被redis立即執行,而是放到客戶端的「命令佇列」裡去(伺服器返回queued字樣,表示命令已經入隊),當客戶端發出exec命令(表示客戶端需要執行事務),這時候redis從客戶端的「命令佇列」裡依次取出命令執行,eg:
redis 127.0.0.1:6379> multi # 客戶端進入事務狀態
okredis 127.0.0.1:6379> set name diaocow
queued # 命令已經入隊
redis 127.0.0.1:6379> set age 25
queued
redis 127.0.0.1:6379> get name
queued
redis 127.0.0.1:6379> get country
queued
redis 127.0.0.1:6379> exec # 執行事務
1) ok
2) ok
3) "diaocow"
4) (nil)
用一幅圖來總結客戶端的命令執行流程就是:
[img]
剛才例子中所對應的命令佇列:
[img]
前面說到,當客戶端處於「事務」狀態,所以命令都不會立即執行,而是被放到「命令佇列」快取起來,其實不準確!當客戶端處於「事務」狀態下,如果遇到下面幾個命令,依然會立即執行:
[table]
|[b]multi[/b]|redis返回錯誤,不允許事務巢狀|
|[b]discard[/b]|丟棄當前事務(該命令只能在事務狀態下使用,否則redis返回錯誤)|
|[b]exec[/b]|執行事務, 把「命令佇列」裡的命令依次取出、執行,並且把執行結果放入到乙個「回答佇列」中,當所有命令執行完後,redis把這個「回答佇列」傳送給客戶端(該命令只能在事務狀態下使用,否則redis返回錯誤)|
|[b]watch[/b]|監視某些鍵,如果在事務執行期間,有乙個「監視」的鍵被修改,那麼事務執行失敗(該命令必須在multi命令之前執行,否則redis返回錯誤)|
[/table]
命令實現偽**:
def multicommand(client):
# 不允許事務巢狀
if client.flag & redis_multi:
return "error"
client.flag &= redis_multi
def discardcommand(client):
# discard命令只能在事務狀態下使用
if not (client.flags & redis_multi):
return "error"
# 重置客戶端的事務狀態(1.釋放命令佇列;2.重置客戶端為非事務狀態;3.取消之前監視的所有鍵)
resetclientmultistate(client)
def execcommand(client):
# exec命令只能在事務狀態下使用
if not (client.flags & redis_multi):
return "error"
# 若某些監視的鍵被修改或者命令入隊錯誤(命令不存在?引數錯誤?),則事務執行失敗
if client.flag & (redis_dirty_cas|redis_dirty_exec):
resetclientmultistate()
return "error"
# 依次執行命令佇列裡的命令
for cmd, argc, argv in client.multistate.commands:
result.add(call(cmd, argc, argv))
resetclientmultistate(client)
return result
discard和exec命令比較容易理解,下面我們重點看下watch命令的作用以及實現原理:
watch命令用來在事務開始之前,監視任意數量的鍵,當呼叫exec命令執行事務時,如果其中任意乙個鍵被其他客戶端修改,那麼整個事務將不再執行,直接返回失敗,eg:
[img]
[b]那這一切redis是怎麼實現的呢?[/b]
原理:redisdb中維護了乙個watched_keys字典,字典的鍵就是這個資料庫中被「監視」的鍵,鍵值就是所有「監視」該鍵的客戶端列表(list型別)
[img]
[size=xx-small][i](上圖只是字典的簡化畫法,若嚴格按照字典結構畫不僅較為麻煩並且不利於闡述主要思想;關於redis字典詳情,請參看 [url=字典[/url]章節)[/i][/size]
當乙個客戶端執行watch命令時:
a. redis會把它加入到被「監視」鍵的客戶端列表中;
b. 同時,客戶端自己也會維護乙個watched_keys列表,用來儲存自己所有監視的鍵(這個屬性有什麼用?是不是和redisdb中的watched_keys屬性有重疊?我們稍後會說)
當任何乙個會觸發鍵內容變更的命令執行後(譬如set),touchwatchedkey函式會被呼叫:它檢查資料庫的watched_keys字典,看該鍵是否正在被「監視」,如果有,那麼該鍵所關聯的所有客戶端列表都將開啟[b]redis_dirty_cas[/b]選項(事務被破壞),然後當客戶端執行exec命令時,如果發現[b]redis_dirty_cas[/b]選項開啟,則事務執行失敗,整個過程用偽**表示就是:
def touchwatchedkey(redisdb, key):
# 獲取監視該鍵的客戶端列表
client_list = redisdb.watched_keys.get(key)
if client_list is none: return
# 開啟客戶端的redis_dirty_cas選項(告訴它們事務已經被破壞)
for client in client_list:
client.flag &= redis_dirty_cas
剛才我們還提到過,客戶端自己也維護了乙個watched_keys屬性——用來儲存自己所監視的鍵,那麼這個屬性有什麼用呢? 我自己覺得是出於效率考慮的:
1. 防止監視相同的鍵,當redis發現該客戶端已經監視了某個鍵,則跳過該鍵,不做任何處理;
2. 當某個客戶端(譬如a)執行完事務(或成功或失敗),客戶端需要清除自己之前所有監視的鍵,如果客戶端自己沒有維護自己監視了哪些鍵,那麼redis就必須遍歷整個redisdb.watched_keys字典,然後在每乙個客戶端列表中查詢a並刪除,效率非常低下,但若客戶端自己維護了所監視鍵的列表,那麼就不在需要遍歷整個字典做清除,eg偽**:
[b]總結:[/b]
2. 了解事務執行原理
3. 了解watch命令作用以及實現原理
redis學習筆記 事務
事務是乙個單獨的隔離操作 事務中的所有命令都會序列化 按順序地執行。事務在執行的過程中,不會被其他客戶端傳送來的命令請求所打斷。事務是乙個原子操作 事務中的命令要麼全部被執行,要麼全部都不執行。注 對於redis事務是否是原子性可以參考我個人挺支援作者觀點。命令說明 multi 標記乙個事務塊的開始...
Redis 原始碼學習之 Redis 事務Nosql
redis事務提供了一種將多個命令請求打包,然後一次性 按照順序地執行多個命令的機制,並且在事務執行的期間,伺服器不會中斷事務而去執行其他不在事務中的命令請求,它會把事務中所有的命令都執行完畢才會去執行其他的命令。howredis中提供了multi discard exec watch unwatc...
Redis學習筆記 事務和鎖
本文是自己的學習筆記,學習資料如下 b站狂神說redis教程 2 redis實現樂觀鎖 redis事務不保證原子性,本質是一組命令的集合。就是將一組命令放進乙個佇列裡一條條執行,發生錯誤就觸發錯誤處理機制,之前已經執行成功的命令也不會回退。同時也說明,事務可以保證一組命令能順序執行。redis可以通...