修改器内置脚本编写_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 script numkeys key [key ...] arg [arg ...]

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 val2
OK

执行以上命令,通过 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 模块机制及常见面试问题解答

1a1c94b26a3f39f330e238775f3a82c0.png

928750e67cb0edadef7844974cc004bc.png在看点这里0b4e9a75929d3369a32627d0b2a9049f.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575141.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

项目上线,部署到服务器(腾讯服务器),http协议及https协议(微信小程序必须https协议才可发布)

一、准备服务器&#xff1a; 1.选择自己的服务器&#xff0c;这里有很多服务器比如&#xff0c;阿里服务器&#xff0c;腾讯服务器等&#xff0c;这里我选择腾讯服务器做测试&#xff0c;其官网&#xff1a;https://cloud.tencent.com/&#xff0c;微信扫码登录后如下进入首页…

队列的基本操作_算法与数据结构(五) 栈和队列

? 工欲善其事&#xff0c;必先利其器。栈和队列 - Stack And Queue栈如何理解栈呢&#xff1f;后进者先出&#xff0c;先进者后出&#xff0c;这就是典型的 "栈" 结构。04_栈和队列-栈结构从栈的操作特性上来看&#xff0c;栈是一种“操作受限”的线性表&#xff0c…

八邻域轮廓跟踪算法_结合mRMR选择和IFCM聚类的遥感影像分类算法

随着遥感成像技术的发展&#xff0c;高分遥感影像空间分辨率逐年提高&#xff0c;已经成为获取地物信息的主要数据来源之一。高分影像分类作为遥感影像处理的重要任务之一&#xff0c;在地理国情普查与监测、数字城市建设、城市规划等领域具有广阔的应用前景。高分影像具有纹理…

node.js中net模块、node实现tcp通信

node.js中net 一、net模块感知&#xff1a; net模块用于创建基于TCP&#xff08;或IPC&#xff09;通信的服务器或客户端&#xff0c;它是nodejs内置模块&#xff0c;直接使用require(‘net’)的方式引入&#xff0c;类似http模块。 二、创建一个tcp服务器&#xff1a; //1…

struts2--文件上传大小

struts2--文件上传大小 Struts2文件上传的大小限制问题 问题&#xff1a;上传大文件报错……解决&#xff1a;修改struts.xml文件中的参数如下<constant name"struts.multipart.maxSize" value"55000000"/><action name"UploadFile" cl…

element-plus中导航高亮不自动刷新问题

解决使用element-plus el-submenu时页面被this.$router.push(“xxx”)跳转后&#xff0c;导航菜单高亮不能自动刷新问题&#xff0c;需要注意以下几点&#xff1a; 在el-menu加上routerindex必须绑定路由的path,参考上面的例子&#xff0c;/不能少default-active设为当前路由&…

计算机二级考试python怎么报名_计算机二级报名流程和条件

对于第一次参加全国计算机二级考试的考生来说&#xff0c;他们对于计算机二级报名流程和条件不是清楚&#xff0c;小编这就来给大家梳理一下。计算机二级报名流程 一、报名 分为网上报名和现场报名。 网上报名&#xff1a;考生在规定时间内登录本省计算机资格网站&#xff0c;按…

操作对象_DOM进阶——HTML属性操作(对象属性)

上一节我们在“DOM基础”学习了对元素节点的操作&#xff0c;这两节介绍对属性节点的操作。属性节点操作有两种方式&#xff0c;一种是使用“对象属性”&#xff0c;另一种是“对象方法”。本节主要介绍“对象属性”的方式。对属性节点的操作涉及两种操作&#xff0c;分别是获取…

三级菜单数据实现,实现嵌套三级菜单数据

//将数据库中通过关联&#xff08;inner join&#xff09;查询多张表没有嵌套但有嵌套关系的数据进行处理&#xff0c;得到具有嵌套层级且嵌套的父级二级属性不可重复出现&#xff0c;具体实现如下&#xff1a;// 通过关联查询到数据库的数据&#xff08;格式&#xff09;&…

sap相关性不能被编译_经典综述编译丨生物硝化抑制丨NAT PLANTS:现代农业中的氮转化和生物硝化抑制作用...

点击蓝字↑↑↑“农作未来(FarmingFuture)”&#xff0c;轻松关注&#xff0c;农作制度研究与您同行&#xff01;编译&#xff1a;贾蓉 排版&#xff1a;王上原创微文&#xff0c;欢迎转发转载。文章信息原名&#xff1a;Nitrogen transformations in modern agriculture and …

Hadoop学习笔记(一)从官网下载安装包

Hadoop是一个分布式系统基础架构&#xff0c;由Apache基金会所开发。用户能够在不了解分布式底层细节的情况下&#xff0c;开发分布式程序。充分利用集群的威力进行快速运算和存储。要学习Hadoop从下载安装包開始打开Hadoop的官方站点&#xff0c;点击Download Hadoop或点击“G…

版本之间如何兼容_Spring Boot 2.4 版本的系统运行要求

名字Servlet 版本Tomcat 9.04.0Jetty 9.43.1Undertow 2.04.0Spring Boot 2.4.2-SNAPSHOT 版本要求至少 Java 8 及其以上版本&#xff0c;目前最高能够支持到 Java 15 &#xff08;包含 15&#xff09;的版本。 Spring 框架&#xff08;Framework&#xff09; 5.3.2 的版本或者以…

JavaScript异步处理问题,循环处理异步任务,并拿到数据,Nodejs循环异步任务接口处理

一、问题描述及解答&#xff1a; 在Promise(异步事件)中&#xff0c;通过遍历的方式处理数据&#xff0c;最后将带有数据的Promise通过return返回&#xff0c;在async/await处理机制中的到的数据是一个空数据组[]或不完整的数据&#xff0c;如下&#xff1a; 使用定时器后数据…

tomcat lifecyclelistener_大公司程序员带你死磕Tomcat系列(五)——容器

死磕Tomcat系列(5)——容器回顾在死磕Tomcat系列(1)——整体架构中我们简单介绍了容器的概念&#xff0c;并且说了在容器中所有子容器的父接口是Container。在死磕Tomcat系列(2)——EndPoint源码解析中&#xff0c;我们知道了连接器将请求过来的数据解析成Tomcat需要的ServletR…

获取当前周一日期_Excel工作表中最全的时间和日期函数,效率、办公必备

在Excel工作表中&#xff0c;函数也可以分为好几类&#xff0c;今天&#xff0c;小编带大家学习时间和日期函数。一、Excel工作表日期函数&#xff1a;Date。功能&#xff1a;返回特定日期的序列号。语法结构&#xff1a;Date(年,月,日)。目的&#xff1a;将制定的“年”、“月…

这样就算会了PHP么?-11

PHP中关于类的基本内容练习&#xff1a; <?phpclass SportObject{public $name;public $height;public $avirdupois;public function __construct($name, $height,$avirdupois) {$this->name $name;$this->height $height;$this->avirdupois $avirdupois;}func…

一个黑色全屏的计时器_我入手了一个1000多的智能手环,值吗?|Fitbit Charge 4测评...

入手Fitbit Charge 4了。作为一个喜欢晚上做运动的Boy&#xff0c;每次运动带着手机确实有够累赘&#xff0c;比如跑步的时候&#xff0c;掏手机看真的很麻烦&#xff0c;但手环只需抬手即可看时间、心率、步数这些&#xff0c;确实很方便。而且&#xff0c;有了手环之后&#…

python没有用_你可能没有在Python3中使用但却应该使用的东西

Python部落(python.freelycode.com)组织翻译&#xff0c;禁止转载&#xff0c;欢迎转发。 由于Python EOL的发布&#xff0c;许多人开始将他们的Python版本从2切换到3。不幸的是&#xff0c;我发现大多数Python3看起来仍然像Python2&#xff0c;但是要加括号(尽管在我之前的文章…

boost库 bind/function的使用

Boost::Function 是对函数指针的对象化封装&#xff0c;在概念上与广义上的回调函数类似。相对于函数指针&#xff0c;function除了使用自由函数&#xff0c;还可以使用函数对象&#xff0c;甚至是类的成员函数&#xff0c;这个就很强大了哈 #include <boost/function.hpp&g…

10恢复出厂设置_Mac系统如何恢复出厂设置

苹果Mac电脑在什么情况下需要恢复出厂设置呢&#xff1f;例如系统数据损坏、遇到无法卸载的恶意软件、错误更新导致、或者你只是想要闲鱼出售你的Mac电脑&#xff0c;这里系统派教你Mac如何恢复出厂设置。我们先简单将恢复出厂分成两步&#xff0c;一是擦除硬盘数据&#xff0c…