1. 程式人生 > >Redis事務學習筆記

Redis事務學習筆記

Redis事務

先來一張思維導圖 在這裡插入圖片描述

事務提供了一種“將多個命令打包,然後一次性的、按順序的執行”的機制,並且事務在執行的期間不會主動中斷,也就是說伺服器在執行完事務中所有的命令後,才會繼續處理其他客戶端的其他命令

相關的命令

multi #開啟一個事務
OK

# 事務邏輯命令

redis> set book-name "kkk"
QUEUED
redis> get book-name
QUEUED
redis> exec #事務的執行
1) OK
2) "kkk"

# 其他命令
redis> discard #事務的取消
redis> watch #在事務開始之前鎖定任意數量的鍵

從上面的命令執行情況中,我們可以看出完成一個事務需要三步:

  1. 開始事務(multi)
  2. 命令入隊 (相關命令)
  3. 執行事務

開啟事務

multi命令執行代表著事務的開始。

該命令的作用就是,將客戶端的redis_multi選項開啟,讓客戶端從非事務狀態切換到事務狀態。

命令入隊

當客戶端處於非事務狀態下時,所有傳送給服務端的精靈都會立即被伺服器執行,可是在事務狀態下,伺服器收到來自客戶端的命令時,不會立即執行命令,而是將這些命令全部發進一個事務佇列裡,然後返回queued,表示命令已入隊。

在這裡,事務佇列實際上是一個數組,每個陣列項都包含了三個屬性:

  1. 要執行的命令
  2. 命令的引數
  3. 引數的個數

執行事務

當客戶端進入到事務狀態後,客戶端傳送的命令就會被放到事務佇列裡,但除了exec,discard,multi,watch這四個命令,這四個命令會直接執行。

  1. 當exec執行時,伺服器根據客戶端所儲存的事務佇列,以先進先出(FIFO)的方式執行事務佇列中的命令。

  2. 執行事務中的命令的結果同樣會以FIFO的順序儲存到一個回覆佇列中。

  3. 當所有命令執行完畢後,exec會把回覆佇列作為自己的執行結果返回給客戶端。並把redis_multi標識去除,客戶端從事務狀態返回到非事務狀態。

事務狀態和非事務狀態執行命令的區別

  • 非事務狀態下命令以單個命令為單位,前一個命令和後一個名字的客戶端不一定是同一個;事務狀態則是以一個事務為單位,執行事務佇列中的所有命令,除非當前事務執行完畢,否則伺服器不會主動中斷事務,也不會執行其他客戶端的其他命令。
  • 在非事務狀態下,執行命令所得的結果會立刻返回客戶端;而事務狀態下則是所有命令的結果集合到回覆佇列,再做為exec命令的結果返回給客戶端。

事務狀態下的discard,multi,watch命令

  • discard用於取消一個事務,他清空客戶端的整個事務佇列,然後見客戶端從事務狀態調整回非事務狀態,最後返回字串ok給客戶端,說明事務已取消。
  • redis事務是不可以巢狀的。所以當客戶端處於事務狀態,而客戶端又傳送一個multi時,伺服器只是簡單地向客戶端傳送一個錯誤,然後繼續等待其他命令的入隊。
  • watch只能在開啟事務狀態之前使用,如果客戶端在事務狀態下,客戶端又傳送了一個watch命令的話,伺服器的處理跟重複傳送multi一樣。
  • 在事務狀態開啟時,傳送multi和watch都只是引發一個錯誤,但不會造成整個事務失敗,也不會修改事務佇列中已有的資料;如果發生別的錯誤,比如傳送一條不存在的命令的話,該事務就會abort。

帶watch的事務

watch命令用於在事務開始之前見識任意數量的key,當呼叫exec的時候,被watch的keys如果被其他客戶端修改了,那麼整個事務不再執行,直接返回失敗(nil)。

watch命令的實現(watch命令是一個樂觀鎖)

watched_keys的實現

在每個代表資料庫的redis.h/redisDb資料結構中,都維護了一個watched_keys字典,字典的鍵是這個資料庫被監視的鍵,而字典的值則是監控這個鍵的客戶端連結串列,結構如圖所示:

watch命令實際就是將被監視的key與當前客戶端在watched_keys中進行關聯。

Redis通過watched_keys字典,如果想檢查某個鍵是否被監視,那麼它只要檢查字典中是否有這個鍵即可;如果進一步想要檢視監視這個鍵的客戶端,那麼只需要取出值,然後遍歷該連結串列即可。

watch的觸發

在任何資料庫鍵空間進行修改的命令執行成功之後(比如flushdb,set,del,lpush,sadd,zrem等),multi.h/touchWatchedKey函式都會被呼叫——它是用來檢查watched_keys字典,看是否有客戶端在監視已經被命令修改的鍵,如果有的話,程式將遍歷該鍵的客戶端連結串列,將這些客戶端的redis_dirty_cas選項開啟。

當客戶端傳送exec時,服務端首先會對客戶端的狀態進行檢查:

  • 如果該客戶端的redis_dirty_cas選項已經被開啟,那麼說明被客戶端監視的鍵至少有一個已經被修改了,事務的安全性已經被破壞。伺服器就會放棄這個事務的執行,直接返回空,表示事務執行失敗。
  • 如果redis_dirty_cas選項沒有被開啟,那麼說明所有監視鍵都安全,伺服器正式執行事務。

watched_keys的移除

當一個客戶端結束他的事務時,無論事務是否成功執行,watched_keys字典中和這個客戶端相關的資料都會被清除。

對Redis的watch採用樂觀鎖的思考

像其他技術裡的併發處理,都是把樂觀鎖放在一個初級的位置(一般會從樂觀鎖繼續進行鎖的升級),然後很多技術還積極使用悲觀鎖來保證使用者高併發下的安全性,但為什麼Redis僅僅採用了樂觀鎖的思路呢?

我覺得在於Redis是單執行緒的,不存在鎖的問題,所以無法實現重量級鎖。

事務的ACID特性

原子性

原子性指的是“多個操作當做一個整體來執行,要麼都執行,要麼都不執行”。

對於redis的事務功能來說,事務佇列中的命令要麼就全都執行,要麼就一個都不執行。因此,redis事務是具有原子性的。

redis事務是依靠錯誤命令進入事務佇列時發生錯誤而被伺服器拒絕執行實現的,例子如下面:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name kkk
QUEUED
127.0.0.1:6379> get
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

redis事務與傳統的關係型資料庫事務的最大區別在於,Redis不支援事務回滾機制,即使事務佇列中的某個命令在執行期出現了錯誤,整個事務也會繼續執行下去,直到所有的事務佇列中所有命令都執行完畢為止。舉例如下面:

127.0.0.1:6379> set msg immsg
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name kkk
QUEUED
127.0.0.1:6379> rpush msg ll1 ll2
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) "kkk"

我們可以看到,即使rpush執行出錯,事務的後續命令也會繼續執行下去,並且之前執行的命令也不會有影響。

這裡應該深刻理解原子性與回滾機制的關係。

原子性:多個操作作為一個整體執行,要麼都執行,要麼都不執行

回滾:事務若執行過程中出現錯誤,應該將資料庫恢復到事務執行之前的狀態

一致性

一致性指的是:如果資料庫在執行事務前是一致的,那麼在執行事務之後,無論事務是否執行成功,資料庫也應該仍然是一致的。

這裡的“一致”指的是資料符合資料庫本身的定義和要求,沒有包含非法或者無效資料。

Redis事務可能出錯在三個地方,入隊錯誤,執行錯誤,伺服器停機,以下是對這三個錯誤對Redis事務的一致性影響的分析

入隊錯誤

比如在入隊時期,輸入一條不存在的命令而發生了入隊錯誤,當最後執行exec時,redis伺服器會拒絕執行入隊過程中出現錯誤的事務,所以redis事務的一致性不會被入隊錯誤所影響。

by the way,redis在2.6.5以前是會執行發生過入隊錯誤的事務的,只不過會跳過發生錯誤的入隊命令,相當於錯誤命令沒有進入事務佇列,也是不會影響一致性。

執行錯誤

執行錯誤時在事務的執行期間發生的錯誤

  • 執行錯誤只會在命令實際執行時才會觸發,入隊時發現不了。類似於Java中編譯期通過,執行期發生錯誤。
  • 執行錯誤發生後,事務不會中斷,他會繼續執行剩餘命令,並且已執行的命令及其執行結果也不會被這個出錯命令所影響。

執行錯誤會被伺服器識別出來,並進行相應的錯誤處理,所以這些錯誤命令不會對資料庫做任何修改,也就不會對事務的一致性產生任何影響。

伺服器停機

無論是無持久化執行,還是aof持久化,還是rdb持久化執行,事務執行中途發生的停機都不會影響資料庫的一致性。

隔離性

隔離性是指即使資料庫中有多個事務併發的執行,各個事物之間也不會相互影響,並且在併發狀態下執行的事務和序列執行的事務產生的結果完全相同。

因為Redis是單執行緒的,所以他以序列化的方式執行事務,並且伺服器保證,在執行事務期間不會對事務進行中斷,因此Redis的事務總是以序列化的方式執行的,所以其事務執行具有隔離性。

永續性

永續性指的是當事務執行完畢之後,執行這個事務所得的結果已經被儲存到永久性儲存介質了,即使伺服器在事務執行完畢之後停機,執行事務所得到的結果也不會丟失。

Redis事務的永續性依賴於Redis是否採用持久化模式以及採用哪種持久化策略。

當且僅當Redis採用AOF持久化模式且採用always策略時,伺服器會儲存每一條修改的命令,這時候Redis事務具有永續性。

參考文章

  • redis設計與實現