詳解事務模式和Lua腳本,帶你吃透Redis 事務

来源:https://www.cnblogs.com/huaweiyun/archive/2023/04/10/17303129.html
-Advertisement-
Play Games

摘要:Redis事務包含兩種模式:事務模式和Lua腳本。 本文分享自華為雲社區《一文講透 Redis 事務》,作者: 勇哥java實戰分享。 準確的講,Redis事務包含兩種模式:事務模式和Lua腳本。 先說結論: Redis的事務模式具備如下特點: 保證隔離性; 無法保證持久性; 具備了一定的原子 ...


摘要:Redis事務包含兩種模式:事務模式和Lua腳本。

本文分享自華為雲社區《一文講透 Redis 事務》,作者: 勇哥java實戰分享。

準確的講,Redis事務包含兩種模式:事務模式和Lua腳本。

先說結論:

Redis的事務模式具備如下特點:

  • 保證隔離性;
  • 無法保證持久性;
  • 具備了一定的原子性,但不支持回滾;
  • 一致性的概念有分歧,假設在一致性的核心是約束的語意下,Redis 的事務可以保證一致性。

但Lua腳本更具備實用場景,它是另一種形式的事務,他具備一定的原子性,但腳本報錯的情況下,事務並不會回滾。Lua腳本可以保證隔離性,而且可以完美的支持後面的步驟依賴前面步驟的結果。

Lua腳本模式的身影幾乎無處不在,比如分散式鎖、延遲隊列、搶紅包等場景。

1 事務原理

Redis的事務包含如下命令:

事務包含三個階段:

  1. 事務開啟,使用 MULTI , 該命令標志著執行該命令的客戶端從非事務狀態切換至事務狀態 ;
  2. 命令入隊,MULTI 開啟事務之後,客戶端的命令並不會被立即執行,而是放入一個事務隊列 ;
  3. 執行事務或者丟棄。如果收到 EXEC 的命令,事務隊列里的命令將會被執行 ,如果是 DISCARD 則事務被丟棄。

下麵展示一個事務的例子。

 redis> MULTI 
 OK
 redis> SET msg "hello world"
 QUEUED
 redis> GET msg
 QUEUED
 redis> EXEC
 1) OK
 1) hello world

這裡有一個疑問?在開啟事務的時候,Redis key 可以被修改嗎?

在事務執行 EXEC 命令之前 ,Redis key 依然可以被修改。

在事務開啟之前,我們可以 watch 命令監聽 Redis key 。在事務執行之前,我們修改 key 值 ,事務執行失敗,返回 nil 。

通過上面的例子,watch 命令可以實現類似樂觀鎖的效果 。

2 事務的ACID

2.1 原子性

原子性是指:一個事務中的所有操作,或者全部完成,或者全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

第一個例子:

在執行 EXEC 命令前,客戶端發送的操作命令錯誤,比如:語法錯誤或者使用了不存在的命令。

 redis> MULTI
 OK
 redis> SET msg "other msg"
 QUEUED
 redis> wrongcommand  ### 故意寫錯誤的命令
 (error) ERR unknown command 'wrongcommand' 
 redis> EXEC
 (error) EXECABORT Transaction discarded because of previous errors.
 redis> GET msg
 "hello world"

在這個例子中,我們使用了不存在的命令,導致入隊失敗,整個事務都將無法執行 。

第二個例子:

事務操作入隊時,命令和操作的數據類型不匹配 ,入隊列正常,但執行 EXEC 命令異常 。

 redis> MULTI 
 OK
 redis> SET msg "other msg"
 QUEUED
 redis> SET mystring "I am a string"
 QUEUED
 redis> HMSET mystring name  "test"
 QUEUED
 redis> SET msg "after"
 QUEUED
 redis> EXEC
 1) OK
 2) OK
 3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
 4) OK
 redis> GET msg
 "after"

這個例子里,Redis 在執行 EXEC 命令時,如果出現了錯誤,Redis 不會終止其它命令的執行,事務也不會因為某個命令執行失敗而回滾 。

綜上,我對 Redis 事務原子性的理解如下:

  1. 命令入隊時報錯, 會放棄事務執行,保證原子性;
  2. 命令入隊時正常,執行 EXEC 命令後報錯,不保證原子性;

也就是:Redis 事務在特定條件下,才具備一定的原子性 。

2.2 隔離性

資料庫的隔離性是指:資料庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。

事務隔離分為不同級別 ,分別是:

  • 未提交讀(read uncommitted)
  • 提交讀(read committed)
  • 可重覆讀(repeatable read)
  • 串列化(serializable)

首先,需要明確一點:Redis 並沒有事務隔離級別的概念。這裡我們討論 Redis 的隔離性是指:併發場景下,事務之間是否可以做到互不幹擾。

我們可以將事務執行可以分為 EXEC 命令執行前和 EXEC 命令執行後兩個階段,分開討論。

  • EXEC 命令執行前

在事務原理這一小節,我們發現在事務執行之前 ,Redis key 依然可以被修改。此時,可以使用 WATCH 機制來實現樂觀鎖的效果。

  • EXEC 命令執行後

因為 Redis 是單線程執行操作命令, EXEC 命令執行後,Redis 會保證命令隊列中的所有命令執行完 。 這樣就可以保證事務的隔離性。

2.3 持久性

資料庫的持久性是指 :事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。

Redis 的數據是否持久化取決於 Redis 的持久化配置模式 。

  1. 沒有配置 RDB 或者 AOF ,事務的持久性無法保證;
  2. 使用了 RDB模式,在一個事務執行後,下一次的 RDB 快照還未執行前,如果發生了實例宕機,事務的持久性同樣無法保證;
  3. 使用了 AOF 模式;AOF 模式的三種配置選項 no 、everysec 都會存在數據丟失的情況 。always 可以保證事務的持久性,但因為性能太差,在生產環境一般不推薦使用。

綜上,redis 事務的持久性是無法保證的 。

2.4 一致性

一致性的概念一直很讓人困惑,在我搜尋的資料里,有兩類不同的定義。

  • 維基百科

我們先看下維基百科上一致性的定義:

Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct. Referential integrity guarantees the primary key – foreign key relationship.

在這段文字里,一致性的核心是“約束”,“any data written to the database must be valid according to all defined rules ”。

如何理解約束?這裡引用知乎問題 如何理解資料庫的內部一致性和外部一致性,螞蟻金服 OceanBase 研發專家韓富晟回答的一段話:

“約束”由資料庫的使用者告訴資料庫,使用者要求數據一定符合這樣或者那樣的約束。當數據發生修改時,資料庫會檢查數據是否還符合約束條件,如果約束條件不再被滿足,那麼修改操作不會發生。

關係資料庫最常見的兩類約束是“唯一性約束”和“完整性約束”,表格中定義的主鍵和唯一鍵都保證了指定的數據項絕不會出現重覆,表格之間定義的參照完整性也保證了同一個屬性在不同表格中的一致性。

“ Consistency in ACID ”是如此的好用,以至於已經融化在大部分使用者的血液里了,使用者會在表格設計的時候自覺的加上需要的約束條件,資料庫也會嚴格的執行這個約束條件。

所以事務的一致性和預先定義的約束有關,保證了約束即保證了一致性。

我們細細品一品這句話: This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct。

寫到這裡可能大家還是有點模糊,我們舉經典轉賬的案例。

我們開啟一個事務,張三和李四賬號上的初始餘額都是1000元,並且餘額欄位沒有任何約束。張三給李四轉賬1200元。張三的餘額更新為 -200 , 李四的餘額更新為2200。

從應用層面來看,這個事務明顯不合法,因為現實場景中,用戶餘額不可能小於 0 , 但是它完全遵循資料庫的約束,所以從資料庫層面來看,這個事務依然保證了一致性。

Redis 的事務一致性是指:Redis 事務在執行過程中符合資料庫的約束,沒有包含非法或者無效的錯誤數據。

我們分三種異常場景分別討論:

  1. 執行 EXEC 命令前,客戶端發送的操作命令錯誤,事務終止,數據保持一致性;
  2. 執行 EXEC 命令後,命令和操作的數據類型不匹配,錯誤的命令會報錯,但事務不會因為錯誤的命令而終止,而是會繼續執行。正確的命令正常執行,錯誤的命令報錯,從這個角度來看,數據也可以保持一致性;
  3. 執行事務的過程中,Redis 服務宕機。這裡需要考慮服務配置的持久化模式。
  • 無持久化的記憶體模式:服務重啟之後,資料庫沒有保持數據,因此數據都是保持一致性的;
  • RDB / AOF 模式: 服務重啟後,Redis 通過 RDB / AOF 文件恢複數據,資料庫會還原到一致的狀態。

綜上所述,在一致性的核心是約束的語意下,Redis 的事務可以保證一致性。

  • 《設計數據密集型應用》

這本書是分散式系統入門的神書。在事務這一章節有一段關於 ACID 的解釋:

Atomicity, isolation, and durability are properties of the database,whereas consistency (in the ACID sense) is a property of the application. The application may rely on the database’s atomicity and isolation properties in order to achieve consistency, but it’s not up to the database alone. Thus, the letter C doesn’t really belong in ACID.

原子性,隔離性和持久性是資料庫的屬性,而一致性(在 ACID 意義上)是應用程式的屬性。應用可能依賴資料庫的原子性和隔離屬性來實現一致性,但這並不僅取決於資料庫。因此,字母 C 不屬於 ACID 。

很多時候,我們一直在糾結的一致性,其實就是指符合現實世界的一致性,現實世界的一致性才是事務追求的最終目標。

為了實現現實世界的一致性,需要滿足如下幾點:

  1. 保證原子性,持久性和隔離性,如果這些特征都無法保證,那麼事務的一致性也無法保證;
  2. 資料庫本身的約束,比如字元串長度不能超過列的限制或者唯一性約束;
  3. 業務層面同樣需要進行保障 。

2.5 事務特點

我們通常稱 Redis 為記憶體資料庫 , 不同於傳統的關係資料庫,為了提供了更高的性能,更快的寫入速度,在設計和實現層面做了一些平衡,並不能完全支持事務的 ACID。

Redis 的事務具備如下特點:

  • 保證隔離性;
  • 無法保證持久性;
  • 具備了一定的原子性,但不支持回滾;
  • 一致性的概念有分歧,假設在一致性的核心是約束的語意下,Redis 的事務可以保證一致性。

從工程角度來看,假設事務操作中每個步驟需要依賴上一個步驟返回的結果,則需要通過 watch 來實現樂觀鎖 。

3 Lua 腳本

3.1 簡介

Lua 由標準 C 編寫而成,代碼簡潔優美,幾乎在所有操作系統和平臺上都可以編譯,運行。Lua 腳本可以很容易的被 C/C ++ 代碼調用,也可以反過來調用 C/C++ 的函數,這使得 Lua 在應用程式中可以被廣泛應用。

Lua 腳本在游戲領域大放異彩,大家耳熟能詳的《大話西游II》,《魔獸世界》都大量使用 Lua 腳本。Java 後端工程師接觸過的 api 網關,比如 Openresty ,Kong 都可以看到 Lua 腳本的身影。

從 Redis 2.6.0 版本開始, Redis內置的 Lua 解釋器,可以實現在 Redis 中運行 Lua 腳本。

使用 Lua 腳本的好處 :

  • 減少網路開銷。將多個請求通過腳本的形式一次發送,減少網路時延。
  • 原子操作。Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入。
  • 復用。客戶端發送的腳本會永久存在 Redis 中,其他客戶端可以復用這一腳本而不需要使用代碼完成相同的邏輯。

Redis Lua 腳本常用命令:

3.2 EVAL 命令

命令格式:

 EVAL script numkeys key [key ...] arg [arg ...]

說明:

  • script是第一個參數,為 Lua 5.1腳本;
  • 第二個參數numkeys指定後續參數有幾個 key;
  • key [key ...],是要操作的鍵,可以指定多個,在 Lua 腳本中通過KEYS[1], KEYS[2]獲取;
  • arg [arg ...],參數,在 Lua 腳本中通過ARGV[1], ARGV[2]獲取。

簡單實例:

 redis> eval "return ARGV[1]" 0 100 
 "100"
 redis> eval "return {ARGV[1],ARGV[2]}" 0 100 101
 1) "100"
 2) "101"
 redis> eval "return {KEYS[1],KEYS[2],ARGV[1]}" 2 key1 key2 first second
 1) "key1"
 2) "key2"
 3) "first"
 4) "second"

下麵演示下 Lua 如何調用 Redis 命令 ,通過redis.call()來執行了 Redis 命令 。

 redis> set mystring 'hello world'
 OK
 redis> get mystring
 "hello world"
 redis> EVAL "return redis.call('GET',KEYS[1])" 1 mystring
 "hello world"
 redis> EVAL "return redis.call('GET','mystring')" 0
"hello world"

3.3 EVALSHA 命令

使用 EVAL 命令每次請求都需要傳輸 Lua 腳本 ,若 Lua 腳本過長,不僅會消耗網路帶寬,而且也會對 Redis 的性能造成一定的影響。

思路是先將 Lua 腳本先緩存起來 , 返回給客戶端 Lua 腳本的 sha1 摘要。 客戶端存儲腳本的 sha1 摘要 ,每次請求執行 EVALSHA 命令即可。

EVALSHA 命令基本語法如下:

 redis> EVALSHA sha1 numkeys key [key ...] arg [arg ...] 

實例如下:

 redis> SCRIPT LOAD "return 'hello world'"
 "5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
 redis> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0
 "hello world"

4 事務 VS Lua 腳本

從定義上來說, Redis 中的腳本本身就是一種事務, 所以任何在事務里可以完成的事, 在腳本裡面也能完成。 並且一般來說, 使用腳本要來得更簡單,並且速度更快。

因為腳本功能是 Redis 2.6 才引入的, 而事務功能則更早之前就存在了, 所以 Redis 才會同時存在兩種處理事務的方法。

不過我們並不打算在短時間內就移除事務功能, 因為事務提供了一種即使不使用腳本, 也可以避免競爭條件的方法, 而且事務本身的實現並不複雜。

-- https://redis.io/

Lua 腳本是另一種形式的事務,他具備一定的原子性,但腳本報錯的情況下,事務並不會回滾。Lua 腳本可以保證隔離性,而且可以完美的支持後面的步驟依賴前面步驟的結果。

Lua 腳本模式的身影幾乎無處不在,比如分散式鎖、延遲隊列、搶紅包等場景。

不過在編寫 Lua 腳本時,要註意如下兩點:

  1. 為了避免 Redis 阻塞,Lua 腳本業務邏輯不能過於複雜和耗時;
  2. 仔細檢查和測試 Lua 腳本 ,因為執行 Lua 腳本具備一定的原子性,不支持回滾。

 

點擊關註,第一時間瞭解華為雲新鮮技術~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 隨著技術的發展,ASP.NET Core MVC也推出了好長時間,經過不斷的版本更新迭代,已經越來越完善,本系列文章主要講解ASP.NET Core MVC開發B/S系統過程中所涉及到的相關內容,適用於初學者,在校畢業生,或其他想從事ASP.NET Core MVC 系統開發的人員。 ...
  • 本系列文章導航 https://www.cnblogs.com/aierong/p/17300066.html https://github.com/aierong/WpfDemo (自我Demo地址) 0.說明 CommunityToolkit.Mvvm8.1有一個重大更新的功能:源生成器功能,它 ...
  • 本系列文章導航 https://www.cnblogs.com/aierong/category/2297596.html 0.說明 CommunityToolkit.Mvvm包(又名MVVM 工具包,以前名為 Microsoft.Toolkit.Mvvm)是一個現代、快速且模塊化的 MVVM 庫。 ...
  • 哈嘍大家好,我是鹹魚。今天跟大家分享一個關於 Linux 服務(service)相關的案例 案例現象 我在 3 月 31日的時候發表了一篇《shell 腳本之一鍵部署安裝 Nginx》,介紹瞭如何通過 shell 腳本一鍵安裝 Nginx 我腳本中執行了 Nginx 開機自啟動的命令,當我使用 sy ...
  • 600 條最強 Linux 命令總結 每博一文案 你有千萬條微博想寫,可有些根本不重要,後來你才懂那是你怕別人看穿你所以才把真話埋在日常里。你有千萬句話想說,可點開那 個對話框,你根本打不出一個字。你才明白,原來你從一開始就怕別人看穿,所以寧可孤獨。所以你寧可每天嘻嘻哈哈,也不要被人看出來你真的難受 ...
  • 介紹了使用海思 CPU 的機頂盒的固件備份和燒錄。通過 USB-TTL 串口燒錄器 CH340 連接機頂盒,使用華為海思刷機工具 HiTool 創建和修改分區表文件,備份和燒寫固件,通過升級包升級系統。在海納思系統中安裝homeassistant,通過 FTP、WebDAV、Alist雲盤訪問文件,... ...
  • 鎖屏面試題百日百刷,每個工作日堅持更新面試題。請看到最後就能獲取你想要的,接下來的是今日的面試題: 1.解釋一下布隆過濾器原理 在日常生活中,包括在設計電腦軟體時,我們經常要判斷一個元素是否在一個集合中。比如在字處理軟體中,需要檢查一個英語單詞是否拼寫正確(也就是要判斷它是否在已知的字典中);在 ...
  • 函數介紹:substring() 函數用於截取字元串,可從字元串的某一位置開始,向右截取若幹個字元,返回一個特定長度的字元串 功能:返回字元、二進位、文本或圖像表達式的一部分 語法:SUBSTRING ( expression, start, length ) SQL 中的 substring 函數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...