《Redis原始碼學習筆記》事務

2021-09-01 19:36:45 字數 3546 閱讀 8259

[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可以通...