1. 程式人生 > >淺談Redis事務機制

淺談Redis事務機制

前言:redis是Nosql資料庫中使用較為廣泛的非關係型記憶體資料庫,常用於資料快取,共享資源,分散式鎖等。Redis使用了單執行緒架構和I/O多路複用模型來實現高效能的記憶體資料庫服務。

Multi
單個 Redis 命令的執行是原子性的,但 Redis 沒有在事務上增加任何維持原子性的機制,所以 Redis 事務的執行並不是原子性的。
事務可以理解為一個打包的批量執行指令碼,但批量指令並非原子化的操作,中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成後續的指令不做。
Redis 事務可以一次執行多個命令, 並且帶有以下兩個重要的保證:

  • 批量操作在傳送 EXEC 命令前被放入佇列快取。

  • 收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。

  • 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

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

  • 開始事務。

  • 命令入隊。

  • 執行事務。

示例:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr num
QUEUED
127.0.0.1:6379> incr num
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 2

Lua指令碼
Redis 2.6.0及以後版本支援Lua指令碼語言,可以將命令打包執行,實現原子性操作。
Redis 以及Spring等提供的API介面都有一個EVAL命令,用於執行lua指令碼。
具體語法:

EVAL  "script"  num  args...

其中字串script及為lua指令碼,num為需要傳遞的引數,args為引數列表,如果不需要傳遞引數,可寫為:

EVAL  "script"  0

在lua中,對於Redis的操作可以使用命令redis.call()來呼叫,例如需要返回key為zhangsan:phone的value值,即可輸入:

127.0.0.1:6379> EVAL "return redis.call('get',KEYS[1])" 1 zhangsan:phone
"13365378654"

其中call() 函式的第一個引數為命令,第二個為key,第三個可以為value,Redis中,KEYS和ARGV為lua內建全域性變數。例如:

127.0.0.1:6379> EVAL "return redis.call('set',KEYS[1],ARGV[1])" 1 zhangsan:phone 17751033130
OK
127.0.0.1:6379> get zhangsan:phone
"17751033130"

因為EVAL命令執行指令碼是原子性的,所以有時候會用來做些校驗,庫存扣減等操作,將命令打包處理,但建議指令碼不能過長,否則影響其他Redis命令的執行。
另外,Redis提供了指令碼的sha1摘要快取,這樣後續如果script不變的話,只需要呼叫可以通過 SCRIPT LOAD 命令進行,而不需要重複傳遞過長的指令碼內容。

redis Evalsha 命令基本語法如下:

redis 127.0.0.1:6379> EVALSHA sha1 numkeys key [key ...] arg [arg ...] 

引數說明:

  • sha1 : 通過 SCRIPT LOAD 生成的 sha1 校驗碼。
  • numkeys: 用於指定鍵名引數的個數。
  • key [key …]: 從 EVAL 的第三個引數開始算起,表示在指令碼中所用到的那些 Redis 鍵(key),這些鍵名引數可以在
    Lua 中通過全域性變數 KEYS 陣列,用 1 為基址的形式訪問( KEYS[1] , KEYS[2] ,以此類推)。
  • arg [arg …]: 附加引數,在 Lua 中通過全域性變數 ARGV 陣列訪問,訪問的形式和 KEYS 變數類似( ARGV[1]、 ARGV[2] ,諸如此類)。

Redis指令碼常用命令:

EVAL script numkeys key [key ...] arg [arg ...]       ## 執行 Lua 指令碼。

EVALSHA sha1 numkeys key [key ...] arg [arg ...]       ## 執行 Lua 指令碼。

SCRIPT EXISTS script [script ...]       ## 檢視指定的指令碼是否已經被儲存在快取當中。

SCRIPT FLUSH      ## 從指令碼快取中移除所有指令碼。

SCRIPT KILL       ## 殺死當前正在執行的 Lua 指令碼。

SCRIPT LOAD script        ## 將指令碼 script 新增到指令碼快取中,但並不立即執行這個指令碼。

詳情可參考:http://www.runoob.com/redis/redis-scripting.html