Redis事務(wù)(樂觀鎖)

一、什么是事務(wù)
事務(wù)是指一系列操作步驟,這一系列的操作步驟,要么完全地執(zhí)行,要么完全地不執(zhí)行。

比如微博中:A用戶關(guān)注了B用戶,那么A的關(guān)注人列表里面就會有B用戶,B的粉絲列表里面就會有A用戶。
這個關(guān)注與被關(guān)注的過程是由一系列操作步驟構(gòu)成:
(1)A用戶添加到B的粉絲列表里面
(2)B用戶添加到A的關(guān)注列表里面;
這兩個步驟必須全部執(zhí)行成功,整個邏輯才是正確的,否則就會產(chǎn)生數(shù)據(jù)的錯誤,比如A用戶的關(guān)注列表有B用戶,但B的粉絲列表里沒有A用戶;
要保證一系列的操作都完全成功,提出了事務(wù)控制的概念。

二、事務(wù)的ACID原則
當(dāng)事務(wù)處理系統(tǒng)創(chuàng)建事務(wù)時,將確保事務(wù)有某些特性。組件的開發(fā)者們假設(shè)事務(wù)的特性應(yīng)該是一些不需要他們親自管理的特性。這些特性稱為ACID特性。

ACID就是:原子性(Atomicity )、一致性( Consistency )、隔離性或獨立性( Isolation)和持久性(Durabilily)。

  1. 原子性
    原子性屬性用于標(biāo)識事務(wù)是否完全地完成,一個事務(wù)的任何更新要在系統(tǒng)上完全完成,如果由于某種原因出錯,事務(wù)不能完成它的全部任務(wù),系統(tǒng)將返回到事務(wù)開始前的狀態(tài)。

讓我們看一下銀行轉(zhuǎn)帳的例子。如果在轉(zhuǎn)帳的過程中出現(xiàn)錯誤,整個事務(wù)將會回滾。只有當(dāng)事務(wù)中的所有部分都成功執(zhí)行了,才將事務(wù)寫入磁盤并使變化永久化。

為了提供回滾或者撤消未提交的變化的能力,許多數(shù)據(jù)源采用日志機制。例如,SQL Server使用一個預(yù)寫事務(wù)日志,在將數(shù)據(jù)應(yīng)用于(或提交到)實際數(shù)據(jù)頁面前,先寫在事務(wù)日志上。但是,其他一些數(shù)據(jù)源不是關(guān)系型數(shù)據(jù)庫管理系統(tǒng) (RDBMS),它們管理未提交事務(wù)的方式完全不同。只要事務(wù)回滾時,數(shù)據(jù)源可以撤消所有未提交的改變,那么這種技術(shù)應(yīng)該可用于管理事務(wù)。

  1. 一致性
    事務(wù)在系統(tǒng)完整性中實施一致性,這通過保證系統(tǒng)的任何事務(wù)最后都處于有效狀態(tài)來實現(xiàn)。如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用,系統(tǒng)處于有效狀態(tài)。如果在事務(wù)中出現(xiàn)錯誤,那么系統(tǒng)中的所有變化將自動地回滾,系統(tǒng)返回到原始狀態(tài)。因為事務(wù)開始時系統(tǒng)處于一致狀態(tài),所以現(xiàn)在系統(tǒng)仍然處于一致狀態(tài)。

再讓我們回頭看一下銀行轉(zhuǎn)帳的例子,在帳戶轉(zhuǎn)換和資金轉(zhuǎn)移前,帳戶處于有效狀態(tài)。如果事務(wù)成功地完成,并且提交事務(wù),則帳戶處于新的有效的狀態(tài)。如果事務(wù)出錯,終止后,帳戶返回到原先的有效狀態(tài)。

記住,事務(wù)不負(fù)責(zé)實施數(shù)據(jù)完整性,而僅僅負(fù)責(zé)在事務(wù)提交或終止以后確保數(shù)據(jù)返回到一致狀態(tài)。理解數(shù)據(jù)完整性規(guī)則并寫代碼實現(xiàn)完整性的重任通常落在開發(fā)者肩上,他們根據(jù)業(yè)務(wù)要求進(jìn)行設(shè)計。

當(dāng)許多用戶同時使用和修改同樣的數(shù)據(jù)時,事務(wù)必須保持其數(shù)據(jù)的完整性和一致性。因此我們進(jìn)一步研究A C I D特性中的下一個特性:隔離性。

  1. 隔離性
    在隔離狀態(tài)執(zhí)行事務(wù),使它們好像是系統(tǒng)在給定時間內(nèi)執(zhí)行的唯一操作。如果有兩個事務(wù),運行在相同的時間內(nèi),執(zhí)行相同的功能,事務(wù)的隔離性將確保每一事務(wù)在系統(tǒng)中認(rèn)為只有該事務(wù)在使用系統(tǒng)。

這種屬性有時稱為串行化,為了防止事務(wù)操作間的混淆,必須串行化或序列化請求,使得在同一時間僅有一個請求用于同一數(shù)據(jù)。

重要的是,在隔離狀態(tài)執(zhí)行事務(wù),系統(tǒng)的狀態(tài)有可能是不一致的,在結(jié)束事務(wù)前,應(yīng)確保系統(tǒng)處于一致狀態(tài)。但是在每個單獨的事務(wù)中,系統(tǒng)的狀態(tài)可能會發(fā)生變化。如果事務(wù)不是在隔離狀態(tài)運行,它就可能從系統(tǒng)中訪問數(shù)據(jù),而系統(tǒng)可能處于不一致狀態(tài)。通過提供事務(wù)隔離,可以阻止這類事件的發(fā)生。

在銀行的示例中,這意味著在這個系統(tǒng)內(nèi),其他過程和事務(wù)在我們的事務(wù)完成前看不到我們的事務(wù)引起的任何變化,這對于終止的情況非常重要。如果有另一個過程根據(jù)帳戶余額進(jìn)行相應(yīng)處理,而它在我們的事務(wù)完成前就能看到它造成的變化,那么這個過程的決策可能建立在錯誤的數(shù)據(jù)之上,因為我們的事務(wù)可能終止。這就是說明了為什么事務(wù)產(chǎn)生的變化,直到事務(wù)完成,才對系統(tǒng)的其他部分可見。

隔離性不僅僅保證多個事務(wù)不能同時修改相同數(shù)據(jù),而且能夠保證事務(wù)操作產(chǎn)生的變化直到變化被提交或終止時才能對另一個事務(wù)可見,并發(fā)的事務(wù)彼此之 間毫無影 響。這就意味著所有要求修改或讀取的數(shù)據(jù)已經(jīng)被鎖定在事務(wù)中,直到事務(wù)完成才能釋放。大多數(shù)數(shù)據(jù)庫,例如SQL Server以及其他的RDBMS,通過使用鎖定來實現(xiàn)隔離,事務(wù)中涉及的各個數(shù)據(jù)項或數(shù)據(jù)集使用鎖定來防止并發(fā)訪問。

  1. 持久性
    持久性意味著一旦事務(wù)執(zhí)行成功,在系統(tǒng)中產(chǎn)生的所有變化將是永久的。應(yīng)該存在一些檢查點防止在系統(tǒng)失敗時丟失信息。甚至硬件本身失敗,系統(tǒng)的狀態(tài)仍能通過在日志中記錄事務(wù)完成的任務(wù)進(jìn)行重建。持久性的概念允許開發(fā)者認(rèn)為不管系統(tǒng)以后發(fā)生了什么變化,完成的事務(wù)是系統(tǒng)永久的部分。

在銀行的例子中,資金的轉(zhuǎn)移是永久的,一直保持在系統(tǒng)中。這聽起來似乎簡單,但這,依賴于將數(shù)據(jù)寫入磁盤,特別需要指出的是,在事務(wù)完全完成并提交后才寫入磁盤的。

所有這些事務(wù)特性,不管其內(nèi)部如何關(guān)聯(lián),僅僅是保證從事務(wù)開始到事務(wù)完成,不管事務(wù)成功與否,都能正確地管理事務(wù)涉及的數(shù)據(jù) 當(dāng)事務(wù)處理系統(tǒng)創(chuàng)建事務(wù) 時,將確保事務(wù)有某些特性。組件的開發(fā)者們假設(shè)事務(wù)的特性應(yīng)該是一些不需要他們親自管理的特性。這些特性稱為ACID特性。

三、redis事務(wù)
Redis中的事務(wù)(transaction)是一組命令的集合,至少是兩個或兩個以上的命令,redis事務(wù)保證這些命令被執(zhí)行時中間不會被任何其他操作打斷。

redis對事務(wù)控制的實現(xiàn)

1、正常情況
一般為三步:

開啟事務(wù)(MULTI)==> 寫操作 ==> 執(zhí)行事務(wù)(EXEC)

開啟事務(wù)(MULTI):用MULTI命令告訴Redis,接下來要執(zhí)行的命令你先不要執(zhí)行,而是把它們暫時存起來 (開啟事務(wù))

寫操作:N條命令進(jìn)入等待隊列(命令入隊)

執(zhí)行事務(wù)(EXEC):告知redis執(zhí)行前面發(fā)送的兩條命令(提交事務(wù))

127.0.0.1:6379> MULTI  #開啟事務(wù)
OK
127.0.0.1:6379> sadd user1 1   #寫操作
QUEUED
127.0.0.1:6379> SADD user2 2   #寫操作
QUEUED
127.0.0.1:6379> EXEC   #執(zhí)行操作
1) (integer) 1
2) (integer) 1
 
127.0.0.1:6379> keys *  #檢查是否操作執(zhí)行
1) "user1"
2) "user2"

2、異常情況
如果操作語句錯誤


127.0.0.1:6379> MULTI  #開啟事務(wù)
OK
 
127.0.0.1:6379> set key value #第一條操作
QUEUED
 
127.0.0.1:6379> set key  #錯誤操作
(error) ERR wrong number of arguments for 'set' command
 
127.0.0.1:6379> EXEC  #無法執(zhí)行事務(wù),那么第一條正確的命令也不會執(zhí)行,所以key的值不會設(shè)置成功
(error) EXECABORT Transaction discarded because of previous errors.
 
127.0.0.1:6379> keys *  #查看key并沒有生成第一條操作的key
1) "runtime" 
2) "caches"

3、例外情況

127.0.0.1:6379> MULTI #開啟事務(wù)
OK
 
127.0.0.1:6379> set key v1  #操作語句
QUEUED
 
127.0.0.1:6379> INCR key  #此命令錯誤,字符串不能自增
QUEUED                    #但是入隊成功
 
127.0.0.1:6379> EXEC   #事務(wù)依然提交了,key的值被設(shè)置為v1,自增操作執(zhí)行失敗,但整個事務(wù)沒有回滾
1) OK  #語句一成功
2) (error) ERR value is not an integer or out of range  #語句二報錯

4、放棄情況
當(dāng)我們事務(wù)寫到一半,出現(xiàn)了錯誤,或者不想執(zhí)行此事務(wù),可以使用discard放棄事務(wù)

127.0.0.1:6379> MULTI   #開啟事務(wù)
OK
 
127.0.0.1:6379> set age 25  #命令入隊
QUEUED
 
127.0.0.1:6379> set age 30  #命令入隊
QUEUED
 
127.0.0.1:6379> DISCARD  #放棄事務(wù),則命令隊列不會被執(zhí)行
OK
 
127.0.0.1:6379> keys *  #查看已有key
1) "runtime"
2) "key"
3) "caches"

5、復(fù)雜情況
5.1 悲觀鎖
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會修改該數(shù)據(jù),所以每次在拿數(shù)據(jù)的時候都會先上鎖,這樣別人想拿這個數(shù)據(jù)就會block阻塞直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖,讓別人無法操作該數(shù)據(jù)。

5.2 樂觀鎖(防止并發(fā)操作)
樂觀鎖(Optimistic Lock),顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改該數(shù)據(jù),所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這條數(shù)據(jù),一般使用版本號機制進(jìn)行判斷。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量。

樂觀鎖大多數(shù)情況是基于數(shù)據(jù)版本號(version)的機制實現(xiàn)的。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標(biāo)識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表添加一個“version”字段來實現(xiàn)讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加1。此時,將提交數(shù)據(jù)的版本號與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本號進(jìn)行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當(dāng)前版本號,則予以更新,否則認(rèn)為是過期數(shù)據(jù),不予更新。

樂觀鎖實現(xiàn)舉例:

5.3 Redis的watch機制實現(xiàn)樂觀鎖
監(jiān)視一個(或多個) key ,如果在事務(wù)exec執(zhí)行之前這個(或這些) key 被其他命令所改動,那么事務(wù)將被打斷

下面這個例子:我們在觀察的時候k1值為1,但是事務(wù)執(zhí)行的時候k1值為2,k1被修改了所以不能執(zhí)行事務(wù)。

127.0.0.1:6379> set k1 1 #設(shè)置k1值為1
OK
127.0.0.1:6379> WATCH k1 #監(jiān)視k1 (當(dāng)已經(jīng)開始監(jiān)控k1,則其他客戶端不能修改k1的值)
OK
127.0.0.1:6379> set k1 2 #設(shè)置k1值為2,此操作可能由其他客戶端執(zhí)行
OK
127.0.0.1:6379> MULTI #開始事務(wù)
OK
127.0.0.1:6379> set k1 3 #修改k值為3
QUEUED 
127.0.0.1:6379> EXEC #提交事務(wù),但k1值不會被修改為3,k1的值仍然是2,因為在事務(wù)開啟之前k1的值被修改了
(nil)
127.0.0.1:6379> get k1
"2"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容