天天看點

redis4.0之Lua腳本新姿勢前言Redis中的Lua腳本後記

redis内嵌了lua環境來支援使用者擴充功能,但是出于資料一緻性考慮,要求腳本必須是純函數的形式,也就是說對于一段lua腳本給定相同的參數,寫入redis的資料也必須是相同的,對于随機性的寫入redis是拒絕的。

從redis 3.2開始lua腳本支援随機性寫入,最近在總結4.0的新特性,索性就都歸到4.0裡,友善查閱。

在redis中使用lua腳本不可避免的要用到以下三個指令:eval、evalsha和script,下面我們來簡單介紹一下:

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

evalsha sha1 numkeys key [key ...] arg [arg ...]

script subcommand

redis允許在lua腳本中調用redis.call()或者redis.pcall()來執行redis指令,如果lua腳本對redis的資料做了更改,那麼除了執行腳本本身以外還需要兩個額外的操作:

把這段lua腳本持久化到aof檔案中,保證redis重新開機時可以回放執行過的lua腳本。

把這段lua腳本複制給備庫執行,保證主備庫的資料一緻性。

由于上述兩步,現在就很容易了解為什麼redis要求lua腳本必須是純函數的形式了,想象一下給定一段lua腳本和輸入參數卻得到了不同的結果,這就會造成重新開機前後和主備庫之間的資料不一緻,redis不允許對資料一緻性的破壞。

上一節我們介紹了lua腳本的持久化及主從複制,很明顯随機寫入會對資料一緻性造成破壞,那麼本節就來介紹redis是如何防止lua腳本中随機寫入的。

首先我們來執行一段腳本,嘗試把time指令傳回的目前時間寫入到鍵now中,看下會怎樣:

不出意外的被拒絕了,這是因為在redis中time指令是一個随機指令(時間是變化的),在lua腳本中調用了随機指令之後禁止再調用寫指令,redis中一共有10個随機類指令:

熟悉lua的讀者也許會問,那要是使用math.random()來生成随機數呢?

redis是允許在lua腳本中使用随機數發生器的,不過大家也應該知道生成的其實都是僞随機序列,除非顯示調用math.randomseed()。通常情況下都會選擇系統時間來作為math.randomseed()的參數,然而redis在初始化lua環境時出于安全考慮并沒有加載os庫,是以os.time無法使用,而redis的time指令屬于随機指令就又回到了上面的問題。

這裡小插曲下,redis自己實作了随機數發生器,替換掉了math.randomseed()和math.random(),以保證在不同運作環境下生成的僞随機數序列總是相同的。

綜上所述,redis無法在lua腳本中進行随機寫入,是因為受到了持久化和主從複制的制約,而制約的根本原因是持久化和複制的粒度是整個lua腳本,如果能夠隻把發生更改的資料做持久化和主從複制,那麼就可以化随機為确定,進一步豐富lua在redis中的使用。

ok,從新版本開始,redis提供了redis.replicate_commands()

函數來實作這一功能,把發生資料變更的指令以事務的方式做持久化和主從複制,進而允許在lua腳本内進行随機寫入,下面來舉例說明:

可以看到,相同的腳本隻是在開頭插入了redis.replicate_commands()就可以成功把時間寫入;這是因為執行了redis.replicate_commands()之後,redis就開始使用multi/exec來包圍lua腳本中調用的指令,持久化和複制的隻是<code>*1\r\n$5\r\nmulti\r\n*3\r\n$3\r\nset\r\n$3\r\nnow\r\n$10\r\n1504460595\r\n*1\r\n$4\r\nexec\r\n</code>而不是整個lua腳本,那麼aof檔案和備庫中拿到的就是一個确定的結果。

并且在lua腳本中讀多寫少的情況下,隻持久化和複制寫指令,可以節省重新開機和備庫的cpu時間。

replicate_commands雖好但是也不能亂用,有幾個事項還是需要注意的:

在寫指令之前調用redis.replicate_commands()

當有大流量寫入時不建議用redis.replicate_commands()

慎用redis.set_repl()

繼續閱讀