Redis事務其實是一個樂觀鎖機制

2020-12-11 小碼農談IT

文/小碼農談IT

資料庫有事務,那Redis也有事務。有的人說Redis事務其實不算事務,應該叫具有命令打包功能。那麼,元芳,你怎麼看?

什麼是事務

百科裡面有這樣一段話,事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。我們重點來關注下原子性和隔離性

原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的操作要麼都做,要麼都不做。隔離性(isolation)。一個事務的執行不能被其他事務幹擾。即一個事務內部的操作及使用的數據對並發的其他事務是隔離的,並發執行的各個事務之間不能互相干擾。

學過資料庫的都明白,資料庫的事務運行有三種模式:自動提交事務,顯式事務,隱性事務。

自動提交事務:每條單獨的語句都是一個事務,每個語句都隱含一個commit。一般執行單條的SQL操作都是自動提交事務。

顯式事務:以begin transaction 開始,以commit 或 rollback 結束。一般我們代碼中業務邏輯時開啟事務對應的就是這種模式。

隱性事務: 在前一個事務完成時,新事務隱式啟動,但每個事務仍以commit或rollback顯示結束。

那麼Redis是不是也有多種運行模式呢?狹隘地說其實並沒有。

Redis事務

Redis事務顧名思義,就是Redis中的事務。Redis 事務可以一次執行多個命令, 並且帶有以下三個重要的保證:

批量操作在發送 EXEC 命令前被放入隊列緩存。收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行事務執行過程其他客戶端提交的命令請求不會插入到事務執行命令序列中一個事務從開始到執行會經歷以下三個階段:開始事務,命令入隊,執行事務。

看,Redis的事務就是這麼簡單粗暴。也就是說事務開啟,N條Redis操作先入隊,然後全部入隊完畢後執行exec命令批量執行。其中如果有哪個操作失敗或者錯誤了,剩下的操作繼續執行,並且失敗之前執行的操作也不會自動回滾,這個其實已經不保證執行的原子性了。注意,這點是和DB事務的最大差別。這也是很多人把Redis事務不叫做事務而只叫做具有命令打包功能的原因。引用一個例子來說明。

總結特點:

1.不支持回滾:沒有實現錯誤直接回滾的功能;

2.不保證原子性:redis同一個事務中如果有一條命令執行失敗,其後的命令仍然會被執行,沒有自動支持回滾;

3.隔離性:只保證了事務執行的隔離性。

為什麼不支持回滾呢?其實官方還有個比較合理的說法,覺得還挺有意思:

只有當被調用的Redis命令有語法錯誤時,這條命令才會執行失敗(在將這個命令放入事務隊列期間,Redis能夠發現此類問題),或者對某個鍵執行不符合其數據類型的操作:實際上,這就意味著只有程序錯誤才會導致Redis命令執行失敗,這種錯誤很有可能在程序開發期間發現,一般很少在生產環境發現。Redis已經在系統內部進行功能簡化,這樣可以確保更快的運行速度,因為Redis不需要事務回滾的能力。

watch命令其實是一個樂觀鎖機制

那既然稱作是事務,肯定是要能支持回滾的吧,不然沒有什麼意義了。筆者認為回滾的話可以有兩種方式。

其一,在操作命令被放入隊列的時候也就是在exec之前就判斷邏輯上的錯誤並不執行exec命令,則事務不會被執行;也可以配合DISCARD命令取消事務,放棄執行事務塊內的所有命令。當然,這樣的做法似乎對語法上的錯誤意義不大,因為操作入隊的時候並沒有直接被執行(版本不同存在爭議),也就無法判斷執行是否失敗。(另一種說法是:如果將某個命令放入隊列時發生錯誤,那麼大多數客戶端將會中止事務,並且丟棄這個事務。比如,由於INCR命令的語法錯誤,Redis根本就沒有將這個命令放入事務執行隊列。如果已經被放入了並執行exec,則不管是都存在錯誤,會將事務一直全部執行下去。);

其二,可以使用watch命令來手動回滾。值得一說的是,watch命令保證了執行的原子性(支持整個隊列不執行=自動回滾)和隔離性。但其還是偏向於隔離性的支持,對於語法錯誤導致的失敗仍然還是無法支持自動回滾的,這也再次印證了官方的那個觀點:只有程序錯誤才會導致Redis命令執行失敗,這種錯誤很有可能在程序開發期間發現,一般很少在生產環境發現

Redis Watch 命令用於監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麼事務將被打斷。

Redis使用WATCH命令實現事務的「檢查再設置」(CAS)行為。作為WATCH命令的參數的鍵會受到Redis的監控,Redis能夠檢測到它們的變化。在執行EXEC命令之前,如果Redis檢測到至少有一個鍵被修改了,那麼整個事務便會中止運行,然後EXEC命令會返回一個Null值,提醒用戶事務運行失敗。

我們引用一個例子來說明watch命令。

例子中用戶A對key為balance的值進行了監控,之後用戶B對balance進行了賦值操作,注意此時用戶A監控的balance已經被其他用戶修改了。而後用戶A開啟事務,對balance進行了一次減15的操作,執行exec。事務執行完畢我們查詢balance結果,其值並沒有改變。說明事務被中止或者說被回滾了。那麼問題來了,事務中對debt的加15操作有執行成功嗎?顯然應該也是不執行的。

再次總結特點:

1.支持破壞隔離性時的回滾來同時保證原子性=Redis執行中的Redis自帶的分布式樂觀鎖;

2.語法錯誤導致的原子性破壞還是不支持自動回滾(版本不同有爭議);

好了,很驚訝的是,這其實就是一個活生生的樂觀鎖的例子,回想我們之前Redis實現的單體樂觀鎖,樂觀鎖的原理都一樣。Redis用樂觀鎖機制實現了Redis事務,但是樂觀鎖的弊端也是存在的,在高並發的情況下,失敗的可能性很高;不管是什麼,合適的才是最好的,這個就留給觀者去評判了。

Redis腳本也是事務型

Redis還有個腳本,根據定義,Redis腳本也是事務型的。我們可以想到我們之前用腳本實現的分布式鎖。因此,可以通過Redis事務實現的功能,同樣也可以通過Redis腳本來實現,而且通常腳本更簡單、更快速。

那麼大家都把Redis事務應用在什麼場景呢?

相關焦點

  • Redis事務詳解,Redis事務不支持回滾嗎?
    他們可以讓Redis在一個步驟裡執行一組命令,且能做到如下2個重要保證:事務中的所有命令都是序列化且都是按順序執行的。在一個客戶端執行Redis事務的過程中,不會接收其他任何客戶端對它發出的請求。這保證了這些命令是作為一個單獨的獨立操作執行的。
  • Redis如何實現分布式鎖?
    這種場景其實並不少見,比如電商秒殺活動,庫存數量的更新就會遇到。如果是單機應用,直接使用本地鎖就可以避免。如果是分布式應用,本地鎖派不上用場,這時就需要引入分布式鎖來解決。由此可見分布式鎖的目的其實很簡單,就是為了保證多臺伺服器在執行某一段代碼時保證只有一臺伺服器執行。
  • 分布式鎖解決方案-Redis
    如synchronized關鍵字的作用域其實是一個進程,在這個進程下面的所有線程都能夠進行加鎖。但是多進程就不行了。對於秒殺商品來說,這個值是固定的。但是每個地區都可能有一臺伺服器。這樣不同地區伺服器不一樣,地址不一樣,進程也不一樣。
  • Redis面試突擊專用
    Redis事務 Redis實現分布式鎖Redis 持久化機制Redis是一個支持持久化的內存資料庫,通過持久化機制把內存中的數據同步到硬碟文件來保證數據持久化。當Redis重啟後通過把硬碟文件重新加載到內存,就能達到恢復數據的目的。
  • 總結一波 Redis 面試題
    Redis事務Redis實現分布式鎖Redis 持久化機制Redis是一個支持持久化的內存資料庫,通過持久化機制把內存中的數據同步到硬碟文件來保證數據持久化。當Redis重啟後通過把硬碟文件重新加載到內存,就能達到恢復數據的目的。
  • Python 中 Redis 庫分布式鎖簡單分析
    最簡單的分布式鎖無非就是找到對於多個程序實例而言單一的存在,比如MySQL數據只有一個或Redis只有一個,此時都可以利用這單一的存在構建一個鎖,多個程序實例要執行某段邏輯前必須先獲得這個鎖,然後才能執行。
  • 總結一波 Redis 面試題,收藏起來.
    Redis事務Redis實現分布式鎖Redis 持久化機制 Redis是一個支持持久化的內存資料庫,通過持久化機制把內存中的數據同步到硬碟文件來保證數據持久化。當Redis重啟後通過把硬碟文件重新加載到內存,就能達到恢復數據的目的。
  • 共享鎖、排他鎖、互斥鎖、悲觀鎖、樂觀鎖、行鎖、表鎖、頁面鎖、不可重複讀、丟失修改、讀髒數據
    這就保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A互斥鎖: 在編程中,引入了對象互斥鎖的概念,來保證共享數據操作的完整性。每個對象都對應於一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。
  • 46道Redis面試題,含參考答案!
    事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行,事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。24、Redis 事務相關的命令有哪幾個?MULTI、EXEC、DISCARD、WATCH25、Redis key 的過期時間和永久有效分別怎麼設置?
  • 【187期】出現機率比較大的Redis面試題(含答案)
    Redis 持久化機制 Redis是一個支持持久化的內存資料庫,通過持久化機制把內存中的數據同步到硬碟文件來保證數據持久化。當Redis重啟後通過把硬碟文件重新加載到內存,就能達到恢復數據的目的。給一個我公司處理的案例:背景雙機拿token,token在存一份到redis,保證系統在token過期時都只有一個線程去獲取token;線上環境有兩臺機器,故使用分布式鎖實現。
  • 資料庫常見面試題:樂觀、悲觀鎖,行鎖、表鎖、讀、寫鎖,間隙鎖
    資料庫面試題MySQL資料庫常見面試題:闡述樂觀、悲觀鎖,、表鎖,讀、寫鎖,間隙鎖(重要,好多公司都考)排他鎖就是不能與其他鎖並存,如個事務獲取了個數據的排他鎖,其他事務就不能再獲取該的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據就讀取和修改SS鎖不互斥,SX和XX鎖都互斥根據鎖的特徵悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別會修改
  • 在Laravel中使用Redis鎖解決緩存擊穿問題
    Redis鎖是解決緩存擊穿問題的一個很好的辦法。Laravel 7 中自帶有 \Illuminate\Cache\RedisLock Redis鎖類,直接使用就行,用起來也很方便。RedisLock 的構造函數如下:public function __construct($redis, $name, $seconds, $owner = null){ parent::__construct($name, $seconds, $owner); $this->redis = $redis;}
  • 手把手教你實現基於Redis的分布式鎖
    多機多進程部署的情況,需要依賴一個第三方組件(存儲鎖對象)來實現一個分布式的同步鎖。2. 分布式鎖的必要條件本文主要介紹第三種場景下基於Redis如何實現分布式鎖。現在我們來看看實現一個分布式鎖的必要條件有哪些?
  • 三歪推薦:Redis常見的面試題
    解決方案:加鎖更新,比如請求查詢A,發現緩存中沒有,對A這個key加鎖,同時去資料庫查詢數據,寫入緩存,再返回給用戶,這樣後面的請求就可以從緩存中拿到數據了。將過期時間組合寫在value中,通過異步的方式不斷的刷新過期時間,防止此類現象。
  • MySQL中悲觀鎖和樂觀鎖到底是什麼?
    鎖定力度小,發生鎖衝突概率低,可以實現的並發度高,但是對於鎖的開銷比較大,加鎖會比較慢,容易出現死鎖情況。 頁鎖就是在頁的粒度上進行鎖定,鎖定的數據資源比行鎖要多,因為一個頁中可以有多個行記錄。當我們使用頁鎖的時候,會出現數據浪費的現象,但這樣的浪費最多也就是一個頁上的數據行。頁鎖的開銷介於表鎖和行鎖之間,會出現死鎖。鎖定粒度介於表鎖和行鎖之間,並發度一般。
  • Redis的各項功能解決了哪些問題
    它還內建了複製,lua腳本,LRU,事務等功能,通過redis sentinel實現高可用,通過redis cluster實現了自動分片。以及事務,發布/訂閱,自動故障轉移等等。 綜上所述,Redis提供了豐富的功能,初次見到可能會感覺眼花繚亂,這些功能都是幹嘛用的?都解決了什麼問題?什麼情況下才會用到相應的功能?那麼下面從零開始,一步一步的演進來粗略的解釋下。
  • 5分鐘完全掌握 Redis
    解決方案:加鎖更新,比如請求查詢A,發現緩存中沒有,對A這個key加鎖,同時去資料庫查詢數據,寫入緩存,再返回給用戶,這樣後面的請求就可以從緩存中拿到數據了。將過期時間組合寫在value中,通過異步的方式不斷的刷新過期時間,防止此類現象。
  • 【110期】面試官:Redis分布式鎖如何解決鎖超時問題?
    , 但是, 並沒有解決當鎖已超時而業務邏輯還未執行完的問題, 這樣會導致: A線程超時時間設為10s(為了解決死鎖問題), 但代碼執行時間可能需要30s, 然後redis服務端10s後將鎖刪除, 此時, B線程恰好申請鎖, redis服務端不存在該鎖, 可以申請, 也執行了代碼, 那麼問題來了, A、B線程都同時獲取到鎖並執行業務邏輯, 這與分布式鎖最基本的性質相違背: 在任意一個時刻, 只有一個客戶端持有鎖
  • 從Redis分布式鎖到Redlock的實現,這些運行漏洞你都有發現嗎?
    ①獲取鎖的時間上。如果 Redlock 運用在高並發的場景下,存在 N 個 Master 節點,一個一個去請求,耗時會比較長,從而影響性能。這個好解決,通過上面描述不難發現,從多個節點獲取鎖的操作並不是一個同步操作,可以是異步操作,這樣可以多個節點同時獲取。即使是並行處理的,還是得預估好獲取鎖的時間,保證鎖的 TTL>獲取鎖的時間+任務處理時間。
  • Node.js 中實踐基於 Redis 的分布式鎖實現
    作者簡介:五月君,Nodejs Developer,慕課網認證作者,熱愛技術、喜歡分享的 90 後青年,歡迎關注 Nodejs技術棧 和 Github 開源項目 https://www.nodejs.red認識線程、進程、分布式鎖線程鎖:單線程編程模式下請求是順序的,一個好處是不需要考慮線程安全、資源競爭問題