Redis系列(九)、Redis的“事務”及Lua腳本操作

目錄

"事務"

介紹 

使用事務,成功提交

使用事務,成功回滾

使用事務,語法錯誤,成功觸發回滾

使用事務,執行錯誤,不會觸發回滾  

LUA腳本

介紹

使用lua腳本的好處

應用

例1:記錄IP登錄次數

例2:當10秒內請求3次後拒絕訪問

lua腳本緩存


大家都知道在RDBMS中有事務操作,同樣在Redis中也是支持"事務"的,只是redis支持的是弱事務性,跟我們平時理解上有些不太一樣,下面來看看有哪些不一樣。同時我們也可以通過lua腳本實現redis的事務操作。

Redis系列文章:

Redis系列(一)、CentOS7下安裝Redis6.0.3穩定版

Redis系列(二)、數據類型之字符串String 

Redis系列(三)、數據類型之哈希Hash

Redis系列(四)、數據類型之列表List

Redis系列(五)、數據類型之無序集合Set

Redis系列(六)、數據類型之有序集合ZSet(sorted_set)

Redis系列(七)、常用key命令

Redis系列(八)、常用服務器命令 


"事務"

介紹 

redis提供簡單的事務命令,由multi和exec組成,實際上相當於將多個命令添加到一個執行的集合內,multi爲begin,exec爲commit,discard相當於rollback,watch相當於鎖,對象若在事務執行前被修改則事務被打斷。

因此redis的事務機制爲樂觀鎖,如果在高併發場景下,如果多個客戶端同時對一個key進行了watch,只要有一個客戶端提交成功,其他客戶端的操作都是無效的,因此redis事務不適合在高併發場景下使用,使用lua腳本可以更好的解決事務解決不了的場景。

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

  • 批量操作在發送 EXEC 命令前被放入隊列緩存。
  • 收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。
  • 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

一個事務從開始到執行會經歷以下三個階段

  • 開始事務。
  • 命令入隊。
  • 執行事務。

如果事務中出現錯誤:

  • 還未exec就報錯:如果事務中出現語法錯誤,則事務會成功回滾,整個事務中的命令都不會提交;
  • 成功執行exec後才報錯:如果事務中出現的不是語法錯誤,而是執行錯誤,不會觸發回滾,該事務中僅有該錯誤命令不會提交,其他命令依舊會繼續提交,因此這裏的"事務"打了個引號,和我們通常理解的數據庫事務完全不一樣。
#begin 開啓一個redis事務
multi

#commit 提交事務裏的命令隊列
exec

#rollback 回滾
discard

#鎖,監視一個或多個key,被監視的key若在提交事務前被修改,則事務被打斷
watch key [key ...

#取消監視
unwatch

使用事務,成功提交

使用事務,成功回滾

使用事務,語法錯誤,成功觸發回滾

 

使用事務,執行錯誤,不會觸發回滾  

LUA腳本

介紹

Redis2.6之後新增的功能,我們可以在redis中通過lua腳本操作redis。與事務不同的是事務是將多個命令添加到一個執行的集合,執行的時候仍然是多個命令,會受到其他客戶端的影響,而腳本會將多個命令和操作當成一個命令在redis中執行,也就是說該腳本在執行的過程中,不會被任何其他腳本或命令打斷干擾。正是因此這種原子性,lua腳本纔可以代替multi和exec的事務功能。同時也是因此,在lua腳本中不宜進行過大的開銷操作,避免影響後續的其他請求的正常執行。

使用lua腳本的好處

  1. lua腳本是作爲一個整體執行的.所以中間不會被其他命令插入
  2. 可以把多條命令一次性打包,所以可以有效減少網絡開銷;
  3. lua腳本可以常駐在redis內存中,所以在使用的時候,可以直接拿來複用.也減少了代碼量

應用

redis腳本使用eval命令執行lua腳本,其中numkeys表示lua script裏有多少個key參數,redis腳本根據該數字從後面的key和arg中取前n個作爲key參數,之後的都作爲arg參數:

eval script numkeys key [key ...] arg [arg ...]

例1:記錄IP登錄次數

#利用hash記錄所有登錄的IP次數
#key參數的數量必須和numkey一致,使用key或者argv可以實現一樣的效果。如下面第一個命令裏用了三個key,代表後面的三個參數分別對應腳本里的key1 key2 key3.第二個命令裏用了一個key,代表了後面第一個參數對應腳本里的key1,後面第二和第三個參數對應腳本里的argv1和argv2
eval "return redis.call('hincrby', KEYS[1], KEYS[2], KEYS[3])" 3 h_host host_192.168.145.1 1

eval "return redis.call('hincrby', KEYS[1], ARGV[1], ARGV[2])" 1 h_host host_192.168.145.1 1

例2:當10秒內請求3次後拒絕訪問

#1.給訪問ip的key遞增,2.判斷該訪問次數若爲首次登錄則設置過期時間10秒,3.若不是首次登錄則判斷是否大於3次,若大於則返回0,否則返回1。
eval "local request_times = redis.call('incr',KEYS[1]);if request_times == 1 then redis.call('expire',KEYS[1], ARGV[1]) end;if request_times > tonumber(ARGV[2]) then return 0 end return 1;" 1 test_127.0.0.1 10 3

通過上面的例子也可以看出,我們可以在redis裏使用eval命令調用lua腳本,且該腳本在redis裏作爲單條命令去執行不會受到其餘命令的影響,非常適用於高併發場景下的事務處理。同樣我們可以在lua腳本里實現任何想要實現的功能,迭代,循環,判斷,賦值 都是可以的。

lua腳本緩存

redis腳本也支持將腳本進行持久化,這樣的話,下次再使用就不用輸入那麼長的lua腳本了。事實上使用eval執行的時候也會緩存,eval與load不同的是eval會將lua腳本執行並緩存,而load只會將腳本緩存。相同點是它們都使用sha算法進行緩存,因此只要lua腳本內容相同,eval與load緩存的sha碼就是一樣的。而緩存後的腳本,我們可以使用evalsha命令直接調用,極大的簡化了我們的代碼量,不用重複的將lua腳本寫出來。

#eval 執行腳本並緩存
eval script numkeys key [key ...] arg [arg ...]

#load 緩存lua腳本
SCRIPT LOAD script

#使用緩存的腳本sha碼調用腳本
EVALSHA sha1 numkeys key [key ...] arg [arg ...] 

#使用sha碼判斷腳本是否已緩存
SCRIPT EXISTS sha1 [sha1 ...]

#清空所有緩存的腳本
SCRIPT FLUSH

#殺死當前正在執行的所有lua腳本
SCRIPT KILL

 

希望本文對你有幫助,請點個贊鼓勵一下作者吧~ 謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章