redis學習(九) redis事務和redis指令碼的比較

z1340954953發表於2018-03-11

如果想要實現一組命令原子性的執行,一種方法是使用事務,一種方法使用redis指令碼,可以對比下兩種方式的區別

Redis事務回顧

事務命令:MULTI, EXEC, DIDCARD ,WATCH ,UNWATCH

使用: MULTI : 開啟一個事務,並會生成一個任務佇列,客戶端傳送的指令都會放在任務佇列中,總是返回OK

         EXEC: 執行任務佇列中的命令,成功返回OK,失敗返回nil

         DIDCARD: 清空任務佇列中的所有命令,並取消事務的執行

          WATCH:監控一個或者多個鍵,如果鍵被修改了, 就會阻止後面一個事務的執行,但是不會阻止其他客戶端對鍵的修改(不通過事務),所有的鍵的監控的取消在exec命令執行後,也可以通過執行UNWATCH命令取消對鍵的監控,客戶端斷開連線也會,取消監控

127.0.0.1:6379> get zhouy 
"2222222222"
127.0.0.1:6379> get zhouy
"2222222222"
127.0.0.1:6379> watch zhouy 
OK
127.0.0.1:6379> set zhouy 1993
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set zhouy 1990
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get zhouy
"1993"
127.0.0.1:6379> 

事務沒有執行成功,key的值還是1993,而且watch一個鍵後,對這個鍵修改,如果另一個客戶端不通過事務修改這個鍵,也是能夠執行成功的,這點需要注意

127.0.0.1:6379> watch zhouy
OK
127.0.0.1:6379> set zhouy 11111
OK
127.0.0.1:6379> get zhouy
"9999"

事務的特性: 一致性,原子性(事務中的一組命令,要麼全部成功,要麼全部失敗),永續性,隔離性(多個事務提交不會相互影響,由於redis是單執行緒的,提交的事務按順序執行)

使用watch的場景: 一般在多執行緒環境下,對一個鍵的更改產生了競態錯誤

$value = get key

$value = $value+1;

set key $value 

此時,可以使用watch命令,實現cas的機制。

watch key 

$value = get key 

$value = $value +1

multi

set key $value 

exec

這樣,每個連線執行exec命令前,如果key被其他客戶端修改了,當前連線執行exec就會失敗,可以根據返回值判斷是否設定成功,沒有成功,再次執行。

事務執行錯誤的場景

事務中的錯誤:


  1、錯誤發生在呼叫CALL之前,例如語法錯誤(引數數量錯誤,命令拼寫錯誤....)。

  2、錯誤發生在呼叫CALL之後,引數型別錯誤,例如對一個string型別的key使用list操作。

  對於第一個錯誤,客戶端可以很容易的避免:向佇列中每新增一個命令都會返回“QUEUE”字串,表示新增成功,否則返回一個錯誤;客戶端可以取消事務的執行。

    從版本2.6.5開始,服務端會記錄這種錯誤,在呼叫EXEC命令時,會拒絕這次事務的執行,並且自動取消事務。

  對於第二類錯誤,即使發生了錯誤,Redis不會取消事務的執行。執行結果以會返回給客戶端,由客戶端決定。

Redis不支援事務回滾,執行後產生的影響需要程式設計師,自己去維護

Redis取消事務  DIDCARD命令終止事務

Redis Lua指令碼的特性

和redis事務類似,Lua指令碼的功能是將redis命令打包為一個Lua指令碼,在伺服器端原子執行。和redis的事務特性相比,減少了網路開銷,一次提交打包的命令;原子性執行避免出現watch的競態條件;同時可以被複用,儲存之後繼續使用

在指令碼中呼叫redis命令

call 和 pcall   -- redis.call ('get',key)  ,自動將Redis的資料型別轉為lua資料型別


兩者的區別在於:call如果執行失敗,停止執行,並且返回一個指令碼錯誤,pcall如果執行錯誤,會記錄錯誤,並繼續執行。

從指令碼中返回值

在指令碼中使用return將指令碼的執行結果返回給客戶端,如果沒有執行return語句預設返回nil,在此過程中也會自動將lua型別轉為redis資料型別

EVAL命令執行指令碼

執行格式: EVAL 指令碼內容   key引數的數量  [key...]  [arg...]

127.0.0.1:6379> eval "return redis.call('set',KEYS[1],argv[1]) "  1 testkey helllo
(error) ERR Error running script (call to f_187b569d90860e5f8c7eda2a3b9139e91f659414): @enable_strict_lua:15: user_script:1: Script attempted to access nonexistent global variable 'argv' 
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1]) "  1 testkey helllo
OK

注意到,KEYS ,ARGV這兩個全域性變數是需要大寫的,(Lua語言是區別大小寫的),寫Lua指令碼的時候需要注意

當指令碼不需要任何引數的時候,不能省略這個引數(keynumber需要設定為0)

jedis中如何呼叫

tring script ="local result={} " +   
                " for i,v in ipairs(KEYS) do " +   
                " result[i] = redis.call('get',v) " +   
                " end " +   
                " return result ";  
  
Jedis jedis = new Jedis(ip,port);  
  
jedis.eval(script,keyCount,String … params);  

注意的是,Lua指令碼中不能出現耗時比較長的操作,不能出現死迴圈,否則redis將不接受其他命令,執行去停止指令碼執行了

,redis提供了lua-time-limit引數現在指令碼最長執行時間,預設是5秒

其他命令:

1. 將指令碼加入快取  SCRIPT LOAD 

每次執行eval命令時,redis會將指令碼的sha1摘要加入到指令碼快取中,以便下次客戶端可以使用evalsha命令呼叫指令碼,如果只是加入快取,使用SCRIPT LOAD

2.  是否快取指令碼  SCRIPT EXISTS +指令碼摘要 1:是  0:否

3. SCRIPT FLUSH 清空指令碼快取

4. SCRIPT KILL 終止正在執行的指令碼 ,如果當前執行的指令碼,修改了redis的資料(修改,刪除,更新key),這個命令就不會停止指令碼執行,防止指令碼只執行了一部分,此時只能使用 SHUTDOWN NOSAVE強制停止.

相關文章