添加lua_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] ,诸如此类)。

这里借用一下官网的例子。

76d54f387dd34f32b88707558246cf3e.png

上述脚本直接返回了入参。

  • 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 scriptRedis 127.0.0.1:6379> EVALSHA sha1 numkeys key [key ...] arg [arg ...]

Redis提供了一个SCRIPTLOAD命令,命令后面的script即为Lua脚本。命令将脚本script添加到脚本缓存中,但并不立即执行这个脚本。执行命令后,Redis会返回一个SHA1串,第二个EVALSHA命令即可执行。

需要注意的是,脚本可以在缓存中保留无限长的时间,直到执行完SCRIPT FLUSH。我们来看一下效果。

29590e5b3582ed7f26f9d1e38f0b3837.png

Redis还支持直接执行Lua脚本文件。首先编写并存储一个Lua脚本。

0d50c6311fce47bb29ec6fb14507c80b.png

然后调用Redis-cli –eval命令

1b8d775b3498d0d608da71c9a934f38b.png

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 1else return 0 end

下面我们来测试一下这个脚本。

首先向Redis的指定key compareAndSet:key写入一个值value

578c7859e278f4462dc0a3a8468ab6c6.png

在Redis中执行lua脚本

b142ec05ff9b27b1163ce02ad0b78fab.png

可以看到第一次执行返回1,说明修改成功了;再使用原参数执行时返回0,说明没有做任何修改。我们再查询一下compareAndSet:key这个key

a4afa71b223e6aae0a76507b3fa7d802.png

可以看到compareAndSet:key这个key已经被修改为new_value了。

四、总结

我们通过lua脚本实现了一个简单的compareAndSet操作。

下面我们通过这个例子来验证一下开篇提到的特性。

  • 减少网络开销:不使用脚本的情况下,我们实现一个compareAndSet至少需要与Redis交互两次,而现在只需要执行一次操作即可完成;
  • 原子操作:得益于Redis的设计,Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心出现竞态条件,无需使用事务,感兴趣的可以百度或等待以后后续文章更新;
  • 复用:可以将一系列操作封装成一个Lua脚本,存储在文件或Redis上,下次使用时直接调用即可。

读到这里,希望你已经对Redis+Lua有了一定的了解,并能使用脚本完成一些简单的复合操作。后续还会继续更新一些基于Lua脚本+java程序实现的分布式数据结构,如延迟队列、可重入锁等,感兴趣的小伙伴可以持续关注。

a1a8d708baca600c7dc14bdc663d247b.png

作者:李崇

转自:https://segmentfault.com/a/1190000019994815

lua中文教程,原名:programming in lua 目录 版权声明..............i 译序..i 目录iii 第一篇语言.......1 第0章序言.......1 0.1 序言..........1 0.2 Lua的使用者................2 0.3 Lua的相关资源............3 0.4 本书的体例.................3 0.5 关于本书...3 0.6 感谢..........4 第1章起点.......5 1.1 Chunks.......5 1.2 全局变量...7 1.3 词法约定...7 1.4 命令行方式.................7 第2章类型和值9 2.1 Nil..............9 2.2 Booleans....9 2.3 Numbers...10 2.4 Strings......10 2.5 Functions.12 2.6 Userdata and Threads.12 第3章表达式..13 3.1 算术运算符...............13 3.2 关系运算符...............13 3.3 逻辑运算符...............13 3.4 连接运算符...............14 3.5 优先级.....15 3.6表的构造..15 第4章基本语法................18 4.1 赋值语句.18 4.2 局部变量与代码块(block)......19 4.3 控制结构语句...........20 Programming in Lua iv Copyright ® 2005, Translation Team, www.luachina.net 4.4 break和return语句......23 第5章函数......24 5.1 返回多个结果值.......25 5.2可变参数..27 5.3 命名参数.28 第6章再论函数................30 6.1 闭包........32 6.2 非全局函数...............34 6.3 正确的尾调用(Proper Tail Calls)...36 第7章迭代器与泛型for....40 7.1 迭代器与闭包...........40 7.2 范性for的语义...........42 7.3 无状态的迭代器.......43 7.4 多状态的迭代器.......44 7.5 真正的迭代器...........45 第8章编译·运行·调试47 8.1 require函数.................49 8.2 C Packages.................50 8.3 错误........51 8.4 异常和错误处理.......52 8.5 错误信息和回跟踪(Tracebacks)....53 第9章协同程序................56 9.1 协同的基础...............56 9.2 管道和过滤器...........58 9.3 用作迭代器的协同...61 9.4 非抢占式多线程.......63 第10章完整示例..............68 10.1 Lua作为数据描述语言使用........68 10.2 马尔可夫链算法.....71 第二篇 tables与objects........75 第11章数据结构..............76 11.1 数组......76 11.2 阵和多维数组.........77 11.3 链表......78 11.4 队列和双端队列.....78 11.5 集合和包.................80 11.6 字符串缓冲.............80 第12章数据文件与持久化..................84 12.1 序列化...86 Programming in Lua v Copyright ® 2005, Translation Team, www.luachina.net 第13章 Metatables and Metamethods...92 13.1 算术运算的Metamethods............92 13.2 关系运算的Metamethods............95 13.3 库定义的Metamethods................96 13.4 表相关的Metamethods................97 第14章环境..103 14.1 使用动态名字访问全局变量...103 14.2声明全局变量........104 14.3 非全局的环境.......106 第15章 Packages.............109 15.1 基本方法...............109 15.2 私有成员(Privacy)................111 15.3 包与文件................112 15.4 使用全局表............113 15.5 其他一些技巧(Other Facilities)...115 第16章面向对象程序设计.................118 16.1 类.........119 16.2 继承.....121 16.3 多重继承...............122 16.4 私有性(privacy)...................125 16.5 Single-Method的对象实现方法127 第17章 Weak表...............128 17.1 记忆函数...............130 17.2 关联对象属性.......131 17.3 重述带有默认值的表...............132 第三篇标准库134 第18章数学库................135 第19章 Table库...............136 19.1数组大小................136 19.2 插入/删除..............137 19.3 排序.....137 第20章 String库..............140 20.1 模式匹配函数.......141 20.2 模式.....143 20.3 捕获(Captures).146 20.4 转换的技巧(Tricks of the Trade)151 第21章 IO库..157 21.1 简单I/O模式..........157 21.2 完全I/O 模式........160 Programming in Lua vi Copyright ® 2005, Translation Team, www.luachina.net 第22章操作系统库........165 22.1 Date和Time............165 22.2 其它的系统调用...167 第23章 Debug库..............169 23.1 自省(Introspective)..............169 23.2 Hooks...173 23.3 Profiles.174 第四篇 C API..177 第24章 C API纵览..........178 24.1 第一个示例程序...179 24.2 堆栈.....181 24.3 C API的错误处理..186 第25章扩展你的程序....188 25.1 表操作.189 25.2 调用Lua函数.........193 25.3 通用的函数调用...195 第26章调用C函数..........198 26.1 C 函数..198 26.2 C 函数库................200 第27章撰写C函数的技巧..................203 27.1 数组操作...............203 27.2 字符串处理...........204 27.3 在C函数中保存状态.................207 第28章 User-Defined Types in C........212 28.1 Userdata.................212 28.2 Metatables..............215 28.3 访问面向对象的数据...............217 28.4 访问数组...............219 28.5 Light Userdata........220 第29章资源管理............222 29.1 目录迭代器...........222 29.2 XML解析...............225
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值