Redis進階應用:Redis+Lua指令碼實現複合操作
一、引言
Redis是高效能的key-value資料庫,在很大程度克服了memcached這類key/value儲存的不足,在部分場景下,是對關係資料庫的良好補充。得益於超高效能和豐富的資料結構,Redis已成為當前架構設計中的首選key-value儲存系統。
雖然Redis官網上提供了200多個命令,但做程式設計時還是避免不了為了實現一小步業務邏輯而多次呼叫Redis的情況。
以compare and set場景為例。如果使用Redis原生命令,需要從Redis中獲取這個key,然後提取其中的值進行比對:如果相等就不做處理;如果不相等或者key不存在則將key設定成目標值。僅僅一個單點的compare and set操作就需要與Redis通訊兩次。
此外,這種分散操作無法利用Redis的原子特性,佔用多次網路IO。
今天我們就來探討一下如何優雅地應對上述場景。
二、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。
三、通過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了。
四、總結
我們通過lua指令碼實現了一個簡單的compareAndSet操作。
下面我們通過這個例子來驗證一下開篇提到的特性。
- 減少網路開銷:不使用指令碼的情況下,我們實現一個compareAndSet至少需要與Redis互動兩次,而現在只需要執行一次操作即可完成;
- 原子操作:得益於Redis的設計,Redis會將整個指令碼作為一個整體執行,中間不會被其他命令插入。因此在編寫指令碼的過程中無需擔心出現競態條件,無需使用事務,感興趣的可以百度或等待以後後續文章更新;
- 複用:可以將一系列操作封裝成一個Lua指令碼,儲存在檔案或Redis上,下次使用時直接呼叫即可。
讀到這裡,希望你已經對Redis+Lua有了一定的瞭解,並能使用指令碼完成一些簡單的複合操作。後續還會繼續更新一些基於Lua指令碼+java程式實現的分散式資料結構,如延遲佇列、可重入鎖等,感興趣的小夥伴可以持續關注。
作者:李崇
原文首發 UAVStack智慧運維
來源:宜信技術