深入理解Redis事務、事務異常、樂觀鎖、管道

来源:https://www.cnblogs.com/phpphp/p/18226507
-Advertisement-
Play Games

Redis事務與MySQL事務 不一樣。 原子性:MySQL有Undo Log機制,支持強原子性,和回滾。Redis只能保證事務內指令可以不被干擾的在同一批次執行,且沒有機制保證全部成功則提交,部分失敗則回滾。 隔離性:MySQL的隔離性指多個事務可以併發執行,MySQL有MVCC機制。而Redis ...


Redis事務與MySQL事務

  • 不一樣。
  • 原子性:MySQL有Undo Log機制,支持強原子性,和回滾。Redis只能保證事務內指令可以不被干擾的在同一批次執行,且沒有機制保證全部成功則提交,部分失敗則回滾。
  • 隔離性:MySQL的隔離性指多個事務可以併發執行,MySQL有MVCC機制。而Redis沒有,Redis是事務提交前的指令不會被執行,單線程的環境下,也就不存在事務未提交時,事務內外數據不一致的隔離性問題了。
  • 持久性:MySQL事務先寫Undo Log,並有Redo Log的兩階段提交機制,可以保證持久性。但是Redis持久化機制只有RDB和AOF持久化策略,若事務成功執行且數據剛好被保存,則可以滿足持久性。
  • 一致性:MySQL是指資料庫從一個合法(指符合業務預期)狀態轉換成另一個合法狀態,這種只要Redis執行不出錯,可以保證。

Redis事務

  • 官方文檔:https://redis.io/docs/latest/develop/interact/transactions/
  • 極簡概括:將一批要執行的Redis指令,放入Redis的執行隊列中,事務執行時(不包含事務未提交時) 使其不被併發過來的任務干擾執行。(無法做到嚴格意義上的ACID 4特性)。
  • 適用場景:
    • 性能優化:10條命令傳輸10次執行10次,與1次批量執行10條命令,性能有差異。
    • 樂觀鎖實現:結合Watch可以實現樂觀鎖。
  • 優點:如上的應用場景就是優點。
  • 缺點:無法像MySQL那樣保證原子性、持久性。
  • 關鍵字:mutli(開啟事務),discard(停止事務)、exec(執行事務)、watch(監視指定key)、unwatch(取消監視所有key)。

事務操作實操

測試multi與exec,常規執行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK

測試discard,事務未提交,強行終止,則修改不會生效

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a1
QUEUED
127.0.0.1:6379> set b b1
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get a
"a"
127.0.0.1:6379> get b
"b"

Redis事務異常(語法錯誤導致整個事務執行失敗,非回滾操作)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a2
QUEUED
127.0.0.1:6379> sset b b2
(error) ERR unknown command `sset`, with args beginning with: `b`, `b2`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get a
"a"
127.0.0.1:6379> get b
"b"

Redis事務異常(非語法錯誤引起的部分失敗,無法保證ACID中的A,無回滾機制)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a aa
QUEUED
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> set b bb
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get a
"aa"
127.0.0.1:6379> get b
"bb"

有Redis事務,為什麼又出來了Lua?

  • Redis事務和Lua機制並不衝突,並且要比Redis事務更加強大。
  • 應對併發安全問題:雖然有了Lua的加持,仍不支持事務回滾或者,強原子性(要麼都成功,要麼都回滾),但是Lua可以保證當前的操作不被打斷(無間隙執行),應對併發(例如超賣)問題,Lua能妥善解決。
  • Redis事務不支持流程式控制制,只支持函數調用:配合Lua用於實現無間隙執行的複雜邏輯,這樣的用法非常多。因為高併發下,若單純利用編程語言多次調Redis,實現判斷或迴圈邏輯,這中間有間隙,會有併發問題發生。
    Lua是一門高性能腳本語言,Lua由標準C編寫而成,幾乎在所有操作系統和平臺上都可以編譯、運行。Lua腳本可以很容易的被C/C++代碼調用,也可以反過來調用C/C++的函數,這使得Lua在應用程式中可以被廣泛應用。

關於Redis+Lua是否是原子性執行的爭議問題

https://redis.io/docs/latest/develop/interact/programmability/eval-intro/
對Redis官網進行搜索,出現了原子性的字眼。
原話是:
Blocking semantics that ensure the script’s atomic execution.
Lua lets you run part of your application logic inside Redis. Such scripts can perform conditional updates across multiple keys, possibly combining several different data types atomically.

但是我想了想有矛盾的地方:
MySQL使用了undo log來保證原子性,要麼成功全部執行,要麼失敗全部回滾。
眾所周知,Redis不支持回滾的,那麼ACID的A就沒辦法全部保證,最多是沒有執行期間沒有間隙,不被其它過來的請求影響,引起併發問題。

然後我又看了看阿裡某架構師對此的剖析,跟我設想的一樣:
Redis會把Lua腳本當做一個整體去執行,中間不會被其它的命令插入,但是如果執行過程中出現了錯誤,事務是不會回滾的。
也就意味著執行Lua腳本的過程不可被拆分,不可被中斷,但是遇到錯誤不會回滾。

Redis樂觀鎖

  • 悲觀鎖:很悲觀,認為數據大概率會有併發一致性問題,首次請求過來時加具有互斥性的鎖阻塞其它併發請求,但是Redis是高性能組件,阻塞會帶來性能問題,所以不用悲觀鎖。
  • 樂觀鎖:樂觀,認為數據小概率有併發一致性問題,所以讀數據時不上鎖,但是寫數據時,會判斷一下這個數據是否被改動,從而在舊值的基礎上做修改,如果數據被改動,則失敗掉此次執行。
  • 註意:redis在事務exec或者discard,都會取消對key的watch操作。
  • 解決問題:高併發讀多寫少場景下Redis數據一致性問題。
  • 演變:

假設用戶a賬戶有100元,此時要添加10元

127.0.0.1:6379> set a_money 100
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 110
127.0.0.1:6379> get a_money
"110"

假設用戶a賬戶有110元,此時要添加20元,但是事務未提交期間,已經被其它請求改為了115,然後事務內加了20。
由於是加法,所以值正確,但是事務內的數據一般是不讓改的,很多情況下的自增或者自減,是需要以原數據為基礎基礎為準的(這也是MySQL隔離級別的用意,所以有了當前讀和快照讀的區分)。

終端1
127.0.0.1:6379> get a_money
"110"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 20
QUEUED

終端2
127.0.0.1:6379> get a_money
"110"
127.0.0.1:6379> incrby a_money 5
(integer) 115


終端1
127.0.0.1:6379> get a_money
"110"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 135
127.0.0.1:6379> get a_money
"135"

Redis沒有事務的隔離機制怎麼辦?使用watch加鎖。

終端一
127.0.0.1:6379> watch a_money
OK
127.0.0.1:6379> get a_money
"135"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby a_money 20
QUEUED

終端二模擬其它併發用戶
127.0.0.1:6379> incrby a_money 5
(integer) 140

終端1
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get a_money
"140"
事務沒有成功被執行,因為watch監控了a_money的值,一旦事務執行期間,被事務外的請求鎖修改,則失敗掉此次事務。
樂觀鎖,在此處的體現就是,利用watch監控一下事務執行期間,a_money的值是否被改動。

unwatch 使用

終端1
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> watch a
OK
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a1

終端2,模擬併發過來的用戶請求
127.0.0.1:6379> set a a2
OK

終端1,執行unwatch後,取消了對所有key的監控,執行exec時,就不是nil了。
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get a
"a1

watch部分key,其餘key的反應

終端1
127.0.0.1:6379> set a a
OK
127.0.0.1:6379> set b b
OK
127.0.0.1:6379> watch a
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a1
QUEUED
127.0.0.1:6379> set b b1
QUEUED

終端2
127.0.0.1:6379> set a a2
OK
127.0.0.1:6379> set b b2
OK

終端1,watch a,沒有watch b,事務提交時,被watch的key,可以影響沒有被watch的key。
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get a
"a2"
127.0.0.1:6379> get b
"b2"

管道

  • 官方文檔:https://redis.io/docs/latest/develop/use/pipelining/
  • 極簡概括:將多個指令的操作,一次性發送給Redis,進行批量處理。
  • 解決問題:減少網路開銷,減少頻繁接收命令的開銷(10輪request->exec->response,精簡為1次request->10次exec->1次response),避免多條Redis指令通信往返時間。避免Redis伺服器頻繁的從用戶態到內核態的調用,減少上下文通信時間。
  • 與事務對比:批量處理指令的行為,類似事務。
  • 註意:redis-cli會話內部並未提供管道命令,(但是使用Linux Shell端支持STDIN標準輸入到redis-cli實現管道,例如echo -e "set a aa \n set b bb" | redis-cli --pipe),但redis-server提供了這個機制,管道機制最好用編程語言的客戶端演示。
若在redis-cli會話內部實現管道,會有如下提示:
127.0.0.1:6379> pipe
(error) ERR unknown command `pipe`, with args beginning with: 
127.0.0.1:6379> pipeline
(error) ERR unknown command `pipeline`, with args beginning with:
  • PHP實現:
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$pipe = $redis->pipeline();

$pipe->set('key1', 'value1');
$pipe->set('key2', 'value2');
$pipe->get('key1');
$pipe->get('key2');

$responses = $pipe->exec();

var_dump($responses);

$redis->close();

返回執行的結果
array(4) {
  [0]=>
  bool(true)
  [1]=>
  bool(true)
  [2]=>
  string(6) "value1"
  [3]=>
  string(6) "value2"
}

管道異常情況(Redis語法錯誤)

以PHP為例,經實際測試(set函數缺少參數2),Redis調用語法錯誤(非PHP語法錯誤),會升級為PHP出現致命錯誤,管道流程走不下去。

Fatal error: Uncaught ArgumentCountError: Redis::set() expects at least 2 arguments, 1 given in E:\Host\test\t1.php:7
Stack trace:
#0 E:\Host\test\t1.php(7): Redis->set('a')
#1 {main}
  thrown in E:\Host\test\t1.php on line 7

管道異常情況(Redis執行異常)

經過實測,對字元串進行遞增操作,除了incr返回false外,其餘上下文代碼執行不受影響。

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$pipe = $redis->pipeline();

$pipe->set('a', 'a');
$pipe->incr('a');
$pipe->set('b', 'b');
$pipe->get('a');
$pipe->get('b');

$responses = $pipe->exec();

var_dump($responses);

$redis->close();


array(5) {     
  [0]=>        
  bool(true)
  [1]=>
  bool(false)
  [2]=>
  bool(true)
  [3]=>
  string(1) "a"
  [4]=>
  string(1) "b"
}

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

-Advertisement-
Play Games
更多相關文章
  • 介紹 HMI-Board為 RT-Thread 聯合瑞薩推出的高性價比圖形評估套件,取代傳統的 HMI+主控板硬體,一套硬體即可實現 HMI+IoT+控制的全套能力。依托於瑞薩高性能晶元 RA6M3 及 RT-Thread 軟體生態,HMI-Board 不僅硬體性能強勁,同時軟體生態豐富,助力開發者 ...
  • 系統編程 POSIX信號量 題目 設計一個程式,主線程需要創建2個子線程之後主線程終止,此時進程中有2個子線程A和B,此時進程中有一個臨界資源flag,子線程A獲取觸摸屏坐標並判斷坐標值是否在LCD屏的左上角,如果坐標範圍滿足左上角,則利用條件量和互斥鎖來喚醒子線程B,子線程B的任務是判斷flag ...
  • 目錄題目解析代碼結果展示 題目 解析 ​ 該題主要依靠條件量和互斥鎖來實現線程之間的同步與互斥,分析主線程、線程A和線程B的任務如下: 主線程: 打開LCD屏和觸摸屏的硬體文件,並分別存儲兩個文件的文件描述符,方便後面進行條件判斷。 開啟線程A和線程B。 定義並初始化條件量和互斥量,方便後續線程內進 ...
  • 任務的掛起與恢復的API函數介紹 API函數 描述 vTaskSuspend() 掛起任務 vTaskResume() 恢復被掛起的任務 xTaskResumeFromISR() 在中斷中恢復被掛起的任務 1、掛起任務類似暫停,可恢復; 刪除任務,無法恢復 2、恢復是恢復被掛起任務 3、帶FromI ...
  • VXLAN在雲網路中應用十分廣泛。本文介紹一種方法在兩台Linux主機之間建立簡單的VXLAN隧道,以供學習、研究之用。 ...
  • 很多APP都需要主動向用戶推送消息,這就需要用到長連接的服務,即我們通常提到的websocket,同樣也是使用socket服務,通信協議是基本類似的,在go中用的最多的、也是最簡單的socket服務就是gorilla/websocket,它有21.1K的star,足以說明它的受歡迎程度, 它的git ...
  • 本文測試環境為SQLserver2019 背景 某業務流水錶,會基於固定範圍內的業務編號做寫入以及查詢操作,熱數據的量級在億級別,一個典型的查詢是基於業務編碼查詢最新(時間戳)某種狀態的前N條數據 簡化後的表結構如下 create table TestTable01 ( id bigint iden ...
  • 資料庫引入LLVM之後,可以為具體的查詢生成定製化的機器碼,並儘可能地將數據存儲在CPU的寄存器中進一步加快計算的速度。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...