Redis進階應用:Redis+Lua腳本實現複合操做

1、引言

Redis是高性能的key-value數據庫,在很大程度克服了memcached這類key/value存儲的不足,在部分場景下,是對關係數據庫的良好補充。得益於超高性能和豐富的數據結構,Redis已成爲當前架構設計中的首選key-value存儲系統。java

雖然Redis官網上提供了200多個命令,但作程序設計時仍是避免不了爲了實現一小步業務邏輯而屢次調用Redis的狀況。數據庫

以compare and set場景爲例。若是使用Redis原生命令,須要從Redis中獲取這個key,而後提取其中的值進行比對:若是相等就不作處理;若是不相等或者key不存在則將key設置成目標值。僅僅一個單點的compare and set操做就須要與Redis通信兩次。數組

此外,這種分散操做沒法利用Redis的原子特性,佔用屢次網絡IO。緩存

今天咱們就來探討一下如何優雅地應對上述場景。服務器

2、Redis與Lua

在介紹Lua以前,咱們須要先對這個語言有個初步瞭解。Lua 是一個小巧的腳本語言,幾乎能夠運行在全部操做系統和平臺上。咱們通常不會用Lua處理特別複雜的事務,所以只需瞭解一些lua的基本語法便可。網絡

Redis問世以後,其開發者也意識到了開篇提到的問題,所以Redis從2.6版本開始支持Lua腳本。新版本的Redis還支持Lua Script debug,感興趣的小夥伴能夠去官網的Documentation中找到對應介紹和QuickStart。數據結構

有了Lua腳本以後,使用Redis程序時便可以在如下方面實現顯著提高:架構

  • 減小網絡開銷:原本N次網絡請求的操做,能夠用一個請求完成。原先N次請求的邏輯放在Redis服務器上完成,減小了網絡往返時延;
  • 原子操做:Redis會將整個腳本做爲一個總體執行,中間不會被其餘命令插入。這是一個重要特性,必定要拿小本本記好。至於爲何是一個原子操做,咱們之後再分析;
  • 複用:客戶端發送的腳本會永久存儲在Redis中。這樣其餘客戶端就能夠複用這一腳本,而不須要使用代碼完成一樣的邏輯。

因此如今流傳一句話:要想學好Redis,必會Lua Script。運維

3、經過Lua腳本實現compare and set

接下來咱們就實現一個簡單的compare and set,並經過這個例子感覺一下Lua腳本給Redis使用帶來的全新體驗。分佈式

首先看一下如何讓Redis執行Lua腳本。

3.1 Redis的EVAL

Redis 127.0.0.1:6379> EVAL script  numkeys key [key ...] arg [arg ...]
  • script: 參數是一段 Lua 5.1 腳本程序。腳本沒必要(也不該該)定義爲一個Lua函數。
  • numkeys: 用於指定鍵名參數的個數。
  • key [key ...]: 從 EVAL 的第三個參數開始算起,表示在腳本中所用到的Redis鍵(key)。在Lua中,這些鍵名參數能夠經過全局變量 KEYS 數組,用1爲基址的形式訪問( KEYS[1] ,KEYS[2],依次類推)。
  • arg [arg ...]: 附加參數,在Lua中經過全局變量ARGV數組訪問,訪問的形式和KEYS變量相似( ARGV[1] 、 ARGV[2] ,諸如此類)。

這裏借用一下官網的例子。

上述腳本直接返回了入參。

  • eval爲Redis關鍵字;
  • 第一個引號中的內容就是Lua腳本;
  • 2爲參數個數;
  • key1和key2是KEYS[1]、KEYS[2]的入參;
  • first和second是ARGV[1],ARGV[2]的入參。

你們能夠簡單地將KEYS[1],KEYS[2], ARGV[1],ARGV[2]理解爲佔位符。

3.2 執行腳本文件和緩存腳本

若是隻能在命令行中寫腳本執行,遇到複雜的腳本程序豈不是會抓狂?

下面咱們來看一下,如何讓Redis執行Lua腳本文件,同時也驗證一下lua腳本的複用特性(之後咱們不再須要按期批量刪除某些符合特定規則的key了)。

Redis 127.0.0.1:6379> SCRIPT LOAD  script
Redis 127.0.0.1:6379> EVALSHA sha1  numkeys key [key ...] arg [arg ...]

Redis提供了一個SCRIPTLOAD命令,命令後面的script即爲Lua腳本。命令將腳本script添加到腳本緩存中,但並不當即執行這個腳本。執行命令後,Redis會返回一個SHA1串,第二個EVALSHA命令便可執行。

須要注意的是,腳本能夠在緩存中保留無限長的時間,直到執行完SCRIPT FLUSH。咱們來看一下效果。

Redis還支持直接執行Lua腳本文件。首先編寫並存儲一個Lua腳本。

而後調用Redis-cli –eval命令

Redis-cli –eval命令語法基本與原eval語法相同。

3.3 使用Lua腳本實現compare and set

compareand set的實現邏輯是這樣的:首先獲取Redis中指定key的value,而後與給定值進行比較:若是相等,則將key設定爲目標值並返回一個標識符;若是不相等,則不做任何操做並返回一個標識符。

if Redis.call('get', KEYS[1]) == ARGV[1]  then
     Redis.call('set', KEYS[1], ARGV[2]);
     return 1
else
     return 0 end

下面咱們來測試一下這個腳本。

首先向Redis的指定key compareAndSet:key寫入一個值value

在Redis中執行lua腳本

能夠看到第一次執行返回1,說明修改爲功了;再使用原參數執行時返回0,說明沒有作任何修改。咱們再查詢一下compareAndSet:key這個key

能夠看到compareAndSet:key這個key已經被修改成new_value了。

4、總結

咱們經過lua腳本實現了一個簡單的compareAndSet操做。

下面咱們經過這個例子來驗證一下開篇提到的特性。

  • 減小網絡開銷:不使用腳本的狀況下,咱們實現一個compareAndSet至少須要與Redis交互兩次,而如今只須要執行一次操做便可完成;
  • 原子操做:得益於Redis的設計,Redis會將整個腳本做爲一個總體執行,中間不會被其餘命令插入。所以在編寫腳本的過程當中無需擔憂出現競態條件,無需使用事務,感興趣的能夠百度或等待之後後續文章更新;
  • 複用:能夠將一系列操做封裝成一個Lua腳本,存儲在文件或Redis上,下次使用時直接調用便可。

讀到這裏,但願你已經對Redis+Lua有了必定的瞭解,並能使用腳本完成一些簡單的複合操做。後續還會繼續更新一些基於Lua腳本+java程序實現的分佈式數據結構,如延遲隊列、可重入鎖等,感興趣的小夥伴能夠持續關注。

做者:李崇

原文首發 UAVStack智能運維

來源:宜信技術學院

相關文章
相關標籤/搜索