修改器内置脚本编写_Node.js 中实践 Redis Lua 脚本
對別人的意見要表示尊重。千萬別說:"你錯了。"——卡耐基
Lua 是一種輕量小巧的腳本語言,用標準 C 語言編寫并以源代碼形式開放,其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。由于 Lua 語言具備原子性,其在執行的過程中不會被其它程序打斷,對于并發下數據的一致性是有幫助的。
作者簡介:五月君,Nodejs Developer,慕課網認證作者,熱愛技術、喜歡分享的 90 后青年,歡迎關注 Nodejs技術棧 和 Github 開源項目 https://www.nodejs.red
Redis 的兩種 Lua 腳本
Redis 支持兩種運行 Lua 腳本的方式,一種是直接在 Redis 中輸入 Lua 代碼,適合于一些簡單的腳本。另一種方式是編寫 Lua 腳本文件,適合于有邏輯運算的情況,Redis 使用 SHA1 算法支持對腳本簽名和 Script Load 預先緩存,需要運行的時候通過簽名返回的標識符即可。
下面會分別介紹如何應用 Redis 提供的 EVAL、EVALSHA 兩個命令來實現對 Lua 腳本的應用,同時介紹一些在 Node.js 中該如何去應用 Redis 的 Lua 腳本。
EVAL
Redis 2.6.0 版本開始,通過內置的 Lua 解釋器,可以使用 EVAL 命令對 Lua 腳本進行求值
- script:執行的腳本
- numkeys:指定鍵名參數個數
- key:鍵名,可以多個(key1、key2),通過 KEYS[1] KEYS[2] 的形式訪問
- atg:鍵值,可以多個(val1、val2),通過 ARGS[1] ARGS[2] 的形式訪問
EVAL Redis 控制臺實踐
按照上面命令格式,寫一個實例如下,通過 KEYS[] 數組的形式訪問 ARGV[],這里下標是以 1 開始,KEYS[1] 對應的鍵名為 name1,ARGV[2] 對應的值為 val2
127.0.0.1:6379> EVAL "return redis.call('SET', KEYS[1], ARGV[2])" 2 name1 name2 val1 val2OK
執行以上命令,通過 get 查看 name1 對應的值為 val2
127.0.0.1:6379> get name1"val2"
注意:以上命令如果不使用 return 將會返回 (nil)
127.0.0.1:6379> EVAL "redis.call('SET', KEYS[1], ARGV[2])" 2 name1 name2 val1 val2(nil)
redis.call VS redis.pcall
redis.call 和 redis.pcall 是兩個不同的 Lua 函數來調用 redis 命令,兩個命令很類似,區別是如果 redis 命令中出現錯誤異常,redis.call 會直接返回一個錯誤信息給調用者,而 redis.pcall 會以 Lua 的形式對錯誤進行捕獲并返回。
使用 redis.call
這里執行了兩條 Redis 命令,第一條故意寫了一個 SET_ 這是一個錯誤的命令,可以看到出錯后,錯誤信息被拋出給了調用者,同時你執行 get name2 會得到 (nil),第二條命令也沒有被執行
127.0.0.1:6379> EVAL "redis.call('SET_', KEYS[1], ARGV[2]); redis.call('SET', KEYS[2], ARGV[3])" 2 name1 name2 val1 val2 val3(error) ERR Error running script (call to f_bf814e38e3d98242ae0c62791fa299f04e757a7d): @user_script:1: @user_script: 1: Unknown Redis command called from Lua script
使用 redis.pcall
和上面同樣的操作,使用 redis.pcall 可以看到輸出結果為 (nil) 它的錯誤被 Lua 捕獲了,這時我們在執行 get name2 會得到一個設置好的結果 val3,這里第二條命令是被執行了的。
EVAL "redis.pcall('SET_', KEYS[1], ARGV[2]); redis.pcall('SET', KEYS[2], ARGV[3])" 2 name1 name2 val1 val2 val3(nil)
EVAL 在 Node.js 中實現
ioredis 支持所有的腳本命令,比如 EVAL、EVALSHA 和 SCRIPT。但是,在現實場景中使用它是很繁瑣的,因為開發人員必須注意腳本緩存,并檢測何時使用 EVAL,何時使用 EVALSHA。ioredis 公開了一個 defineCommand 方法,使腳本更容易使用。
const Redis = require("ioredis");const redis = new Redis(6379, "127.0.0.1");
const evalScript = `return redis.call('SET', KEYS[1], ARGV[2])`;
redis.defineCommand("evalTest", {
numberOfKeys: 2,
lua: evalScript,
})
async function eval() {
await redis.evalTest('name1', 'name2', 'val1', 'val2');
const result = await redis.get('name1');
console.log(result); // val2
}
eval();
EVALSHA
EVAL 命令要求你在每次執行腳本的時候都發送一次腳本主體 (script body)。Redis 有一個內部的緩存機制,因此它不會每次都重新編譯腳本,通過 EVALSHA 來實現,根據給定的 SHA1 校驗碼,對緩存在服務器中的腳本進行求值。SHA1 怎么生成呢?通過 script 命令,可以對腳本緩存進行操作
- SCRIPT FLUSH:清除所有腳本緩存
- SCRIPT EXISTS:檢查指定的腳本是否存在于腳本緩存
- SCRIPT LOAD:將一個腳本裝入腳本緩存,但并不立即運行它
- SCRIPT KILL:殺死當前正在運行的腳本
EVALSHA 命令格式
同上面 EVAL 不同的是前面 EVAL script 換成了 EVALSHA sha1
EVALSHA sha1 numkeys key [key ...] arg [arg ...]EVALSHA Redis 控制臺實踐
載入腳本緩存
127.0.0.1:6379> SCRIPT LOAD "redis.pcall('SET', KEYS[1], ARGV[2]);""2a3b189808b36be907e26dab7ddcd8428dcd1bc8"
以上腳本執行之后會返回一個 SHA-1 簽名過后的標識字符串,這個字符串用于下面命令執行簽名之后的腳本
127.0.0.1:6379> EVALSHA 2a3b189808b36be907e26dab7ddcd8428dcd1bc8 2 name1 name2 val1 val2進行 get 操作讀取 name1 的只為 val2
127.0.0.1:6379> get name1"val2"
EVALSHA 在 Node.js 中實現
分為三步:緩存腳本、執行腳本、獲取數據
const Redis = require("ioredis");const redis = new Redis(6379, "127.0.0.1");
const evalScript = `return redis.call('SET', KEYS[1], ARGV[2])`;
async function evalSHA() {
// 1. 緩存腳本獲取 sha1 值
const sha1 = await redis.script("load", evalScript);
console.log(sha1); // 6bce4ade07396ba3eb2d98e461167563a868c661
// 2. 通過 evalsha 執行腳本
await redis.evalsha(sha1, 2, 'name1', 'name2', 'val1', 'val2');
// 3. 獲取數據
const result = await redis.get("name1");
console.log(result); // "val2"
}
evalSHA();
Lua 腳本文件
有邏輯運算的腳本,可以編寫 Lua 腳本文件,編寫一些簡單的腳本也不難,可以參考這個教程 https://www.runoob.com/lua/lua-tutorial.html
Lua 文件
以下是一個測試代碼,通過讀取兩個值比較返回不同的值,通過 Lua 腳本實現后可以多條 Redis 命令的原子性。
-- test.lua-- 先 SET
redis.call("SET", KEYS[1], ARGV[1])
redis.call("SET", KEYS[2], ARGV[2])
-- GET 取值
local key1 = tonumber(redis.call("GET", KEYS[1]))
local key2 = tonumber(redis.call("GET", KEYS[2]))
-- 如果 key1 小于 key2 返回 0
-- nil 相當于 false
if (key1 == nil or key2 == nil or key1 < key2)
then
return 0
else
return 1
end
Node.js 中加載 Lua 腳本文件
和上面 Node.js 中應用 Lua 差別不大,多了一步,通過 fs 模塊先讀取 Lua 腳本文件,在通過 eval 或者 evalsha 執行。
const Redis = require("ioredis");const redis = new Redis(6379, "127.0.0.1");
const fs = require('fs');
async function test() {
const redisLuaScript = fs.readFileSync('./test.lua');
const result1 = await redis.eval(redisLuaScript, 2, 'name1', 'name2', 20, 10);
const result2 = await redis.eval(redisLuaScript, 2, 'name1', 'name2', 10, 20);
console.log(result1, result2); // 1 0
}
test();
▼往期精彩回顧▼入門 Node.js Net 模塊構建 TCP 網絡服務Node.js 核心模塊都在使用的 ?Events 模塊你了解嗎?消息中間件 RabbitMQ 入門篇重磅 | OpenJS 基金會推出 Node.js 專業認證考試分享 10 道 Nodejs EventLoop 和事件相關面試題Docker 容器環境下 Node.js 應用程序的優雅退出Node.js 服務 Docker 容器化應用實踐Node.js 是什么?我為什么選擇它?分享 10 道 Nodejs 進程相關面試題Node.js進階之進程與線程Node.js 中的緩沖區(Buffer)究竟是什么?Node.js 內存管理和 V8 垃圾回收機制淺談 Node.js 模塊機制及常見面試問題解答在看點這里
總結
以上是生活随笔為你收集整理的修改器内置脚本编写_Node.js 中实践 Redis Lua 脚本的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不是有效的函数或过程名_过程和函数
- 下一篇: sql随机抽取数据50条_厉害!苏宁通过