Redis的事務與鎖

2021-08-03 07:37:01 字數 2860 閱讀 5854

一.redis事務

redis的事務和傳統關聯式資料庫的事務並不相同。在關聯式資料庫中,使用者首先向資料庫伺服器傳送begin,然後執行各個相互一致的寫操作和讀操作,最後,使用者可以選擇傳送commit來確認之前所做的修改,或者傳送rollback來放棄那些修改。

在redis裡面也有簡單的方法可以處理一連串相互一致的讀操作和寫操作。redis的事務以特殊命令multi為開始,之後跟著使用者傳入的多個命令,最後以exec為結束。在redis裡面,被multi命令和exec命令包含的所有命令會乙個接乙個地執行,知道所有命令都執行完畢為止。當乙個事務執行完畢之後redis才會處理其他客戶端的命令。當redis從乙個客戶端那裡接收到multi命令時,redis會將這個客戶端之後傳送的所有命令都放入到乙個佇列裡面,直到這個客戶端傳送exec命令為止,然後redis就會在不被打斷的情況下,乙個接乙個地執行儲存在佇列裡面的命令。需要注意的是,在沒有執行exec命令之前,redis只是把傳過來的命令放到佇列裡,並沒有執行真正的操作。

由於這種簡單的事務在exec命令被呼叫之前不會執行任何實際操作,所以使用者將沒辦法根據讀到的資料來做決定。這個問題看上去似乎無足輕重,但實際上無法以一致的形式讀取資料將導致某一型別的問題變得難以解決,除此之外,因為再多個事務同事處理同乙個物件時通常需要用到二階提交,所以如果事務不能以一致的形式讀取資料,那麼二階提交將無法實現,從而導致一些原本可以執行的事務淪落至執行失敗的地步。

redis提供了乙個watch命令,在使用者使用watch命令對redi鍵進行監視之後,直到使用者執行exec命令的這段時間裡面,如果有其他客戶端搶先對任何被監視的鍵進行了替換,更新或刪除等操作,那麼當使用者嘗試執行exec命令的時候,事務將失敗並返回乙個錯誤。通過使用watch,multi/exec,unwatch/discard等命令,程式可以在執行某些重要操作的時候,通過確保自己正在使用的資料沒有發生變化來避免資料出錯。

什麼是discard?unwatch命令可以在watch命令執行之後,multi命令執行之前對連線進行充值,同樣地,discard命令也可以在multi命令執行之後,exec命令執行之前對連線進行重置。這也就是說,使用者在使用watch命令監視乙個或多個鍵,接著使用multi開始乙個新的事務,並將多個命令入隊到事務佇列之後,仍然可以通過傳送discard命令來取消watch命令並清空所有已入隊命令。

二.redis鎖

redis中的鎖是基於命令setnx來實現的,setnx key value, 如果key不存在,就設定key對應字串value。在這種情況下,該命令和set一樣。當key已經存在時,就不做任何操作。setnx是」set if not exists」。鎖要做的就是將乙個隨機生成的128位uuid設定為鍵的值,並使用這個值來防止鎖被其他程序獲得。

當我們需要加鎖的時候,客戶端嘗試使用setnx命令,如果返回1,表示獲取到了鎖,執行需要鎖住的**,然後釋放鎖,需要注意的是如果出現異常導致鎖沒有正常釋放,這個影響還是很大的,這裡我們需要使用expire命令來為key設定超時時長,過了超時時間redis就會將這個key刪除掉,即強制釋放鎖。

使用redis實現分布式鎖在很多場景下十分有用,典型的秒殺場景中就可以使用redis分布式鎖。下面介紹下redis分布式鎖在秒殺場景中的應用

所謂秒殺,從業務角度看,是短時間內多個使用者「爭搶」資源,這裡的資源在大部分秒殺場景裡是商品;將業務抽象,技術角度看,秒殺就是多個執行緒對資源進行操作,所以實現秒殺,就必須控制線程對資源的爭搶,既要保證高效併發,也要保證操作的正確。

剛才提到過,實現秒殺的關鍵點是控制線程對資源的爭搶,根據基本的執行緒知識,可以不加思索的想到下面的一些方法: 

1、秒殺在技術層面的抽象應該就是乙個方法,在這個方法裡可能的操作是將商品庫存-1,將商品加入使用者的購物車等等,在不考慮快取的情況下應該是要運算元據庫

的。那麼最簡單直接的實現就是在這個方法上加上synchronized關鍵字,通俗的講就是鎖住整個方法; 

2、鎖住整個方法這個策略簡單方便,但是似乎有點粗暴。可以稍微優化一下,只鎖住秒殺的**塊,比如寫資料庫的部分; 

3、既然有併發問題,那我就讓他「不併發」,將所有的執行緒用乙個佇列管理起來,使之變成序列操作,自然不會有併發問題。

上面所述的方法都是有效的,但是都不好。為什麼?第一和第二種方法本質上是「加鎖」,但是鎖粒度依然比較高。什麼意思?試想一下,如果兩個執行緒同時執行秒殺方法,這兩個執行緒操作的是不同的商品,從業務上講應該是可以同時進行的,但是如果採用第一二種方法,這兩個執行緒也會去爭搶同乙個鎖,這其實是不必要的。第三種方法也沒有解決上面說的問題。

那麼如何將鎖控制在更細的粒度上呢?可以考慮為每個商品設定乙個互斥鎖,以和商品id相關的字串為唯一標識,這樣就可以做到只有爭搶同一件商品的執行緒互斥,不會導致所有的執行緒互斥。分布式鎖恰好可以幫助我們解決這個問題。

分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。在分布式系統中,常常需要協調他們的動作。如果不同的系統或是同乙個系統的不同主機之間共享了乙個或一組資源,那麼訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。

我們來假設乙個最簡單的秒殺場景:資料庫裡有一張表,column分別是商品id,和商品id對應的庫存量,秒殺成功就將此商品庫存量-1。現在假設有1000個執行緒來秒殺兩件商品,500個執行緒秒殺第乙個商品,500個執行緒秒殺第二個商品。我們來根據這個簡單的業務場景來解釋一下分布式鎖。 

通常具有秒殺場景的業務系統都比較複雜,承載的業務量非常巨大,併發量也很高。這樣的系統往往採用分布式的

架構來均衡負載。那麼這1000個併發就會是從不同的地方過來,商品庫存就是共享的資源,也是這1000個併發爭搶的資源,這個時候我們需要將併發互斥管理起來。這就是分布式鎖的應用。 

而key-value儲存系統,如

redis

,因為其一些特性,是實現分布式鎖的重要工具。

參考:

redis 樂觀鎖與事務

一 樂觀鎖 先表明態度,樂觀鎖並不是乙個好的實現方式!在mysql中,我們一般通過給資料表額外建乙個version欄位,讀的時候讀出verson,更新的時候 v2 versin 1,語句為 update set version version 1 where id and version v2 只要...

Redis事務 事務鎖

一旦成功所有的成功,乙個失敗,所有一些列連續動作都失敗 事務的基本操作 注意 加入事務的命令暫時到任務佇列中,並沒有立即執行,只有執行exec命令才開始執行事務定義過程中發現問題,怎麼辦?discard 事務的工作流程 事務的注意事項 手動進行事務回滾 業務場景1 業務分析 基於特定條件的事務執行 ...

redis11 Redis事務 事務鎖

一旦成功所有的成功,乙個失敗,所有一些列連續動作都失敗 事務的基本操作 multi exec 事務定義過程中發現問題,怎麼辦?discard 事務的工作流程 事務的注意事項 手動進行事務回滾 業務場景1 業務分析 基於特定條件的事務執行 鎖 解決方案 watch key1 key2 unwatch ...