redis數據類型 面試,redis怎么修改_面試官問我Redis事務,還問我有哪些實現方式

 2023-10-05 阅读 27 评论 0

摘要:?「第12期」 距離大叔的80期小目標還有68期,今天大叔要跟大家分享的內容是 —— Reids中的事務。同樣,這也是redis中重要指數為四顆星的必備基礎知識點。下面一起來了解一下吧。?相信大家對Redis并不陌生了吧,對 Redis五種數據類型(String,Hash&
?

「第12期」 距離大叔的80期小目標還有68期,今天大叔要跟大家分享的內容是 —— Reids中的事務。同樣,這也是redis中重要指數為四顆星的必備基礎知識點。下面一起來了解一下吧。

?

相信大家對Redis并不陌生了吧,對 Redis五種數據類型(String,Hash,List,Set, SortedSet) 的使用也應該是得心應手了。今天為什么要跟大家聊聊Redis的事務呢?

首先Redis事務在實際的場景應用上也占著比較重要的地位,例如在秒殺場景中,我們就可以利用Redis事務中的watch命令監聽key,實現樂觀鎖,保證不會出現沖突,也防止商品超賣。

另外就是Redis事務也是面試過程中面試官著重照顧的基礎知識對象,假設面試官問你實現Redis事務有哪些方式?事務發生錯誤時Redis是怎么處理的?Redis事務支持回滾嗎等等這些問題,你是否能脫口而出回答上來呢?如果你對這方便的基礎知識有所欠缺,那是不是就栽跟頭了呢?

所以,這就是大叔想聊聊Redis事務的必要性所在。下面大叔將圍繞以下幾點與大家分享:

  • 什么是Redis事務
  • 實現Redis事務有哪些方式
  • Redis事務是否支持回滾
  • 事務中發生錯誤Redis如何表現
  • Redis事務的實戰應用

什么是Redis事務

redis數據類型 面試。官方給出的定義是這樣子的:

Redis事務可以一次執行多個命令, 并且帶有以下兩個重要的保證:

  • 事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
  • 事務是一個原子操作:事務中的命令要么全部被執行,要么全部都不執行。

官方腔換成方言就是:

Redis事務提供了一種 “將多個命令打包, 然后一次性、按順序地執行” 的機制, 并且事務在執行的期間不會主動中斷 —— 服務器在執行完事務中的所有命令之后, 才會繼續處理其他客戶端的其他命令。

或者你也可以把Redis事務理解為一個隊列,開啟事務后,往后的提交的Redis命令都會依次入隊,遇到觸發當前事務指令時,隊列中的指令會依次被取出并執行。

「值得注意的是」

redis支持事務嗎。“事務中的命令要么全部被執行,要么全部都不執行” 這句話單純想表達的是:“事務執行需要對應的觸發條件(命令)”

下面看個例子先整體了解一下Redis事務:

127.0.0.1:6379>?get?name
"zhangsan"
127.0.0.1:6379>?get?sex
"female"
127.0.0.1:6379>?MULTI??#?開啟事務
OK
127.0.0.1:6379>?set?name?dashu
QUEUED????????????????#?命令入隊
127.0.0.1:6379>?set?sex?male
QUEUED????????????????#?命令入隊
127.0.0.1:6379>?EXEC??#?觸發當前事務
1)?OK
2)?OK
127.0.0.1:6379>?get?name
"dashu"
127.0.0.1:6379>?get?sex
"male"
127.0.0.1:6379>

實現Redis事務有哪些方式

了解完Redis事務是什么回事后,接下來我們繼續看看實現Redis事務有哪些方式。

命令模式

命令模式是實現redis事務比較常見的方式,該方式的主要命令有:MULTI、EXEC、DISCARD、WATCH。

MULTI

MULTI 命令用于開啟一個事務,它總是返回 OK 。

MULTI 執行之后, 客戶端可以繼續向服務器發送任意多條命令,這些命令不會立即被執行, 而是被放到一個隊列中,等待事務被觸發。

EXEC

redis事務回滾,EXEC 命令負責觸發并執行事務中的所有命令

  • 如果客戶端在使用 MULTI 開啟了一個事務之后,卻因為斷線而沒有成功執行 EXEC ,那么事務中的所有命令都不會被執行。
  • 如果客戶端成功在開啟事務之后執行 EXEC ,那么事務中的所有命令都會被執行。

EXEC 命令返回的是一個數組, 數組中的每個元素都是執行事務中的命令所產生的回復。 回復元素的先后順序和命令發送的先后順序一致。

DISCARD

DISCARD 命令可以理解為是搞破壞的。當 DISCARD 命令被執行時, 事務會被丟棄, 事務隊列會被清空, 并且客戶端會從事務狀態中退出。

我們看個例子:

127.0.0.1:6379>?get?name
"dashu"
127.0.0.1:6379>?MULTI
OK
127.0.0.1:6379>?set?name?saycode
QUEUED
127.0.0.1:6379>?DISCARD
OK
127.0.0.1:6379>?get?name
"dashu"
127.0.0.1:6379>

我們可以看到雖然開啟事務后我們重新設置了name的值,但是當我們執行DISCARD命令后,該事務被成功丟棄了,所以當我們再次獲取name的值的時候,我們可以看到它的值并沒有發生改變。

WATCH

WATCH 命令用于在事務開始之前監視任意數量的鍵,當調用 EXEC 命令執行事務時, 如果任意一個被監視的鍵已經被其他客戶端修改了, 那么整個事務不再執行, 直接返回失敗。

redis怎么用、看例子:

  • 首先我們在一個Redis客戶端一上使用 WATCH 命令監控兩個key,分別為name和sex,然后開啟事務,在事務中修改name的值,
  • 在客戶端一執行 EXEC 命令之前,我們另外開一個客戶端二,在客戶端二中我們修改sex的值為man
  • 接著我們回到客戶端一執行 EXEC 命令
#?客戶端一
127.0.0.1:6379>?get?name
"dashu"
127.0.0.1:6379>?get?sex
"male"
127.0.0.1:6379>?WATCH?name?sex
OK
127.0.0.1:6379>?MULTI
OK
127.0.0.1:6379>?set?name?saycode
QUEUED
127.0.0.1:6379>?EXEC
(nil)??????????????????#?事務失敗?
127.0.0.1:6379>?get?sex
"man"
127.0.0.1:6379>?get?name
"dashu"

#---------?這是一條分割線?---------#

#?客戶端二
127.0.0.1:6379>?get?sex
"male"
127.0.0.1:6379>?set?sex?man
OK

從上面執行的結果可以看到,客戶端一中的事務失敗了,事務中所修改的name的值也不成功。主要原因是:調用 EXEC 命令執行事務時,被監控的sex 被客戶端二修改了,所以客戶端一的事務不再執行

WATCH命令的實現

在每個代表數據庫的 redis.h/redisDb 結構類型中, 都保存了一個 watched_keys 字典, 字典的鍵是這個數據庫被監視的鍵, 而字典的值則是一個鏈表, 鏈表中保存了所有監視這個鍵的客戶端。

比如說,以下字典就展示了一個 watched_keys 字典的例子:

595998e3892a4c1372409c7760d4cc70.png

其中, 鍵 key1 正在被 client2 、 client5 和 client1 三個客戶端監視, 其他一些鍵也分別被其他別的客戶端監視著。

WATCH 命令的作用, 就是將當前客戶端和要監視的鍵在 watched_keys 中進行關聯。

redis有什么用?舉個例子, 如果當前客戶端為 client10086 , 那么當客戶端執行 WATCH key1 key2 時, 前面展示的 watched_keys 將被修改成這個樣子:

27ce34ae14878ed11e449f5000eb938c.png

通過watched_keys字典, 如果程序想檢查某個鍵是否被監視, 那么它只要檢查字典中是否存在這個鍵即可; 如果程序要獲取監視某個鍵的所有客戶端, 那么只要取出鍵的值(一個鏈表), 然后對鏈表進行遍歷即可。

WATCH的觸發原理

在任何對數據庫鍵空間(key space)進行修改的命令成功執行之后 (比如FLUSHDB、SET、DEL、LPUSH、SADD、ZREM,諸如此類),multi.c/touchWatchedKey函數都會被調用 —— 它檢查數據庫的watched_keys字典, 看是否有客戶端在監視已經被命令修改的鍵, 如果有的話, 程序將所有監視這個/這些被修改鍵的客戶端的REDIS_DIRTY_CAS選項打開:

ad98162c30f422732bd2eb01485fc4fe.png

當客戶端發送 EXEC 命令、觸發事務執行時, 服務器會對客戶端的狀態進行檢查:

  • 如果客戶端的 REDIS_DIRTY_CAS 選項已經被打開,那么說明被客戶端監視的鍵至少有一個已經被修改了,事務的安全性已經被破壞。服務器會放棄執行這個事務,直接向客戶端返回空回復,表示事務執行失敗。
  • 如果 REDIS_DIRTY_CAS 選項沒有被打開,那么說明所有監視鍵都安全,服務器正式執行事務。

了解完其工作原理后,我們發現該 WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行為。

上面講到的是如何給我們需要的key加監控,那我們應該如何取消監控呢?

  • 實際上,當 EXEC 被調用時, 不管事務是否成功執行, 對所有鍵的監視都會被取消。
  • 另外, 當客戶端斷開連接時, 該客戶端對鍵的監視也會被取消。
  • 使用無參數的 UNWATCH 命令可以手動取消對所有鍵的監視

2、Lua腳本

redis hget。除了上面介紹的命令模式可以實現Redis事務外,其實還有一種非常重要的方式:Lua腳本。

為什么要夸Lua腳本呢?我們來看看Lua腳本有什么優勢:

  • 原子操作:Redis確保腳本執行期間,其它任何腳本或者命令都無法執行。也就是說,在編寫腳本的過程中無需擔心會出現競態條件,無需使用事務。
  • 減少網絡開銷:可以將多個請求通過腳本的形式一次發送,減少網絡時延。因此使用腳本要更簡單,速度更快
  • 復用。客戶端發送的腳本會永久存在redis中,這樣,其他客戶端可以復用這一腳本而不需要使用代碼完成相同的邏輯。

香嗎?真香!反正用過的都說好。可以看到相比命令模式還是優勢還蠻大的。

那么Lua腳本要怎么用呢?下面跟大家介紹幾個常見的常用的命令:

EVAL

EVAL 可以理解為是lua腳本的解釋器,它的語法格式如下:

EVAL?script?numkeys?key?[key?...]?arg?[arg?...]
  • script:一段 Lua 腳本或 Lua 腳本文件所在路徑及文件名。
  • numkeys:Lua 腳本對應參數數量
  • key [key ...]:Lua 中通過全局變量 KEYS 數組存儲的傳入參數
  • arg [arg ...]:Lua 中通過全局變量 ARGV 數組存儲的傳入附加參數

官方腔有點重對吧,沒事,咱們來看個例子:

eval?"return?{KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"?2?key1?key2?first?second

redis持久化。eval的第一個參數是腳本的內容,第二個參數是腳本里面KEYS數組的長度(不包括ARGV參數的個數),這里是兩個;緊接著就會有兩個參數,用于傳遞個KEYS數組;后面剩下的參數全部傳遞給ARGV數組,相當于命令行參數。

127.0.0.1:6379>?eval?"return?{KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"?2?username?age?jack?20
1)?"username"
2)?"age"
3)?"jack"
4)?"20"

redis.call() / redis.call()

如果我們想在lua腳本中調用redis的命令該如何操作?其實我們可以在腳本中使用 redis.call() 或 redis.pcall() 直接調用。兩者用法類似,只是在遇到錯誤時,返回錯誤的提示方式不同。

舉個例子:

127.0.0.1:6379>?get?name
"saycode"
127.0.0.1:6379>?eval?"return?redis.call('set',KEYS[1],'dashu')"?1?name
OK
127.0.0.1:6379>?get?name
"dashu"
127.0.0.1:6379>?eval?"return?redis.call('get','name')"?0
"dashu"
127.0.0.1:6379>

SCRIPT LOAD 和 EVALSHA

  • SCRIPT LOAD:提前載入 Lua 腳本,返回對應腳本的 SHA1 摘要
  • EVALSHA:執行腳本,與EVAL相似,只不過它的參數為腳本的 SHA1 摘要

SCRIPT LOAD 和 EVALSHA 經常配合使用。我們看個例子:

127.0.0.1:6379>?SCRIPT?LOAD?"return?redis.call('set',KEYS[1],'30')"
"6445747e70ce11ad0b9717d78e8ff16fb0faed46"
127.0.0.1:6379>?evalsha?6445747e70ce11ad0b9717d78e8ff16fb0faed46?1?age
OK
127.0.0.1:6379>?get?age
"30"
127.0.0.1:6379>

更多命令可以參看Redis Script 官方文檔

有了上面的知識,我們就可以使用lua腳本來靈活的使用redis的事務,這里舉幾個簡單的例子:

redis面試題?場景1:使用redis限制30分鐘內一個IP只允許訪問5次

思路:每次想把當前的時間插入到redis的list中,然后判斷list長度是否達到5次,如果大于5次,那么取出隊首的元素,和當前時間進行判斷,如果在30分鐘之內,則返回-1,其它情況返回1。我們來看一下具體實現:

eval?"redis.call('rpush',?KEYS[1],ARGV[1]);if?(redis.call('llen',KEYS[1])?>tonumber(ARGV[2]))?then?if?tonumber(ARGV[1])-redis.call('lpop',?KEYS[1])?1?'test_127.0.0.1'?1451460590?5?1800

Lua腳本 對于實現Redis事務確實是一種不錯的選擇,相信未來會有越來越多的開發者傾向于使用腳本來實現事務。不過我們在使用的時候也要注意以下兩點:

  • 注意Redis版本。腳本功能是 Redis 2.6 才引入的。
  • 由于腳本執行的原子性,所以我們不要在腳本中執行過長開銷的程序,否則會驗證影響其它請求的執行。

好了,以上就是實現Redis事務方式的有關內容,如果你之前還沒有了解到第二種腳本方式,趕緊給大叔點贊打call吧哈哈~

我們接著往下看。

Redis事務是否支持回滾

Redis的事務和傳統的關系型數據庫事務的最大區別在于,Redis不支持事務回滾機制(rollback)。

redis,也就是說:當在事務過程中發生錯誤時,Redis事務失敗時并不進行回滾(roll back),而是繼續執行余下的命令。官方給出的理由是這樣子的:

  • 從實用性的角度來說,Redis失敗的命令是由編程錯誤造成的(例如錯誤的語法,命令用在了錯誤類型的命令),而這些錯誤應該在開發的過程中被發現,而不應該出現在生產環境中。
  • 保證Redis性能。因為不需要對回滾進行支持,所以 Redis 的內部可以保持簡單且快速

看個例子:

127.0.0.1:6379>?get?name
"dashu"
127.0.0.1:6379>?MULTI
OK
127.0.0.1:6379>?set?name?saycode
QUEUED
127.0.0.1:6379>?lpop?name
QUEUED
127.0.0.1:6379>?EXEC
1)?OK
2)?(error)?WRONGTYPE?Operation?against?a?key?holding?the?wrong?kind?of?value
127.0.0.1:6379>?get?name
"saycode"
127.0.0.1:6379>

上面例子中,我們在事務中重新設置name的值,并且使用一個命令去操作一個錯誤的數據類型,可以看到最終事務還是成功執行了,同時也會返回事務中發生錯誤的指令的出錯原因

事務中發生錯誤Redis如何表現

實際上,事務的錯誤我們可以總結兩種情況:

  • 一種是:事務在執行 EXEC 之前,入隊的命令可能會出錯。比如命令可能會產生語法錯誤(參數數量錯誤,參數名錯誤,等等),或者其他更嚴重的錯誤,比如內存不足(如果服務器使用 maxmemory 設置了最大內存限制的話)。

對于發生在 EXEC 執行之前的錯誤,客戶端的做法是檢查命令入隊所得的返回值:如果命令入隊時返回 QUEUED ,那么入隊成功;否則,就是入隊失敗。如果有命令在入隊時失敗,那么大部分客戶端都會停止并取消這個事務。看例子:

127.0.0.1:6379>?get?name
"saycode"
127.0.0.1:6379>?get?sex
"man"
127.0.0.1:6379>?MULTI
OK
127.0.0.1:6379>?set?name?dashu
QUEUED
127.0.0.1:6379>?sett?sex?woman
(error)?ERR?unknown?command?`sett`,?with?args?beginning?with:?`sex`,?`woman`,
127.0.0.1:6379>?EXEC
(error)?EXECABORT?Transaction?discarded?because?of?previous?errors.
127.0.0.1:6379>?get?name
"saycode"
127.0.0.1:6379>?get?sex
"man"
  • 還有一種是:命令可能在 EXEC 調用之后失敗。比如事務中的命令可能處理了錯誤類型的鍵,例如將列表命令用在了字符串鍵上面

至于那些在 EXEC 命令執行之后所產生的錯誤, 并沒有對它們進行特別處理: 即使事務中有某個/某些命令在執行時產生了錯誤, 事務中的其他命令仍然會繼續執行。

127.0.0.1:6379>?get?name
"dashu"
127.0.0.1:6379>?MULTI
OK
127.0.0.1:6379>?set?name?saycode
QUEUED
127.0.0.1:6379>?lpop?name
QUEUED
127.0.0.1:6379>?EXEC
1)?OK
2)?(error)?WRONGTYPE?Operation?against?a?key?holding?the?wrong?kind?of?value
127.0.0.1:6379>?get?name
"saycode"
127.0.0.1:6379>

redis高級面試題、我們可以看到:即使事務中有某條/某些命令執行失敗了, 事務隊列中的其他命令仍然會繼續執行 —— Redis 不會停止執行事務中的命令。

Redis事務的實戰應用

了解完Redis事務的基礎,最后我們來寫個Demo來實現樂觀鎖,業務場景是商品搶購,偽代碼如下:

#?樂觀鎖
public?function?actionBuy(){
????$userId?=?mt_rand(1,99999999);
????$goods?=?$this->goods;
????$redis?=?Yii::$app->redis;
????$lock?=?"Huawei?p40";

????try?{
????????$inventory['num']?=?$redis->get('goodNums');
????????if($inventory['num']<=0){
????????????throw?new?\Exception('活動結束');
????????}

????????$redis->watch($lock);
????????$redis->multi();

????????//todo:這里還需要重新判斷下庫存,否則會出現超發,高并發情況下$inventory['num']肯定會出現同時讀取一個值;為了方便測試,沒寫db操作
????????//redis事務是將命令放入隊列中,無法取goodNums來判斷庫存是否結束,此處使用數據庫來判斷庫存合理

????????//業務處理??減庫存,創建訂單
????????$redis->decr('goodNums');
????????$redis->sadd('order',$userId);

????????$redis->exec();

????????Common::addLog('shop.log',$userId.'?搶購成功');
????}catch?(\Exception?$e){
????????$redis->discard();
????????Common::addLog('shop.log',$e->getMessage());
????????throw?new?\Exception('搶購失敗');
????}

????die('success');
}

好了,今天的分享就到這里了,關注公眾號「大叔說碼」 獲取更多干貨,我們下期見~

87930a81371b00f804ba441489d54d12.png

參考:

1、 https://redis.io/topics/transactions

2、https://zhuanlan.zhihu.com/p/146865185

redis面試中常被問到的?3、https://walkingsun.github.io/WindBlog/2019/03/14/redis/

4、https://blog.csdn.net/fangjian1204/article/details/5058508

5、https://redis.io/commands/eval

6、https://techlog.cn/article/list/10183180

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/117593.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息