Redis 是一种十分风行的内存数据库,罕用于数据缓存与高频数据存储。大多数开发人员可能据说过 redis 能够运行 Lua 脚本,然而可能不晓得 redis 在什么状况下须要应用到 Lua 脚本。
一、浏览本文前置条件
- 能够遵循这个链接中的办法在操作系统上装置 Redis
- 如果你对 redis 命令不相熟,查看《Redis 命令援用》
二、为什么须要 Lua 脚本
简而言之:Lua 脚本带来性能的晋升。
- 很多利用的服务工作蕴含多步 redis 操作以及应用多个 redis 命令,这时你能够应用 Redis 联合 Lua 脚本,会为你的利用带来更好的性能。
- 另外蕴含在一个 Lua 脚本外面的 redis 命令具备原子性,当你面对高并发场景下的 redis 数据库操作时,能够无效防止多线程操作产生脏数据。
三、学点 Lua 语法
说了那么多,Lua 不会怎么办?不要慌!Lua 其实很简略,如果你已经学习过任何一门编程语言,学习 Lua 都非常简单。上面给大家举几个例子学习一下:
3.1. 一个简略的例子
Lua 脚本通过各种语言的 redis 客户端都能够调用,咱们就简略一点应用 redis-cli
看上面的 redis 命令行:
eval "redis.call('set', KEYS[1], ARGV[1])" 1 key:name value
EVAL命令行前面跟着的是 Lua 脚本:"redis.call('set', KEYS[1], ARGV[1])"
, 放到编程语言外面就是一段字符串,跟在 Lua 脚本字符串前面的三个参数顺次是:
- redis Lua 脚本所须要的 KEYS 的数量,只有一个 KEYS[1],所以紧跟脚本之后的参数值是 1
- Lua 脚本须要的参数 KEYS[1]的参数值,在咱们的例子中值为 key:name
- Lua 脚本须要的参数 ARGV[1]的参数值,在咱们的例子中值为 value
Lua 脚本中包含两组参数:KEYS[]和ARGV[],两个数组下标从 1 开始。一个值得去恪守的最佳实际是:把 redis 操作所需的 key 通过 KEYS 进行参数传递,其余的 Lua 脚本所需的参数通过 ARGV 进行传递。
下面的脚本执行实现之后,咱们应用上面的 Lua 脚本来进行验证,如果该脚本的返回值是”value”,与咱们之前设置的 key:name 的值雷同,则示意咱们的 Lua 脚本被正确执行了。
eval "return redis.call('get', KEYS[1])" 1 key:name
3.2. 认真看下 Lua 脚本里的内容
咱们的第一个 Lua 脚本只蕴含一条语句,调用redis.call
redis.call('set', KEYS[1], ARGV[1])
所以在 Lua 脚本外面能够通过 redis.call
执行 redis 命令,call 办法的第一个参数就是 redis 命令的名称,因为咱们调用的是 redis 的 set 命令,所以须要传递 key 和 value 两个参数。
咱们第二个脚本不只是执行了一个脚本,因为执行 get 命令还返回了执行后果。留神脚本中有一个 return 关键字。
eval "return redis.call('get', KEYS[1])" 1 key:name
当然如果只是下面的这么简略的 Lua 脚本,还不如间接应用命令行更不便。咱们理论应用到的 Lua 脚本会比下面的简单,下面的 Lua 脚本只是一个 Hello World。
3.3. 简单点的例子
我曾应用 Lua 脚本从一个 hash map 外面依照肯定的程序获取若干 key 对应的值。对应的程序在一个 zset 排序汇合中进行保留,数据设置及排序能够通过上面的实现。
# 设置 hkeys 为键 Hash 值
hmset hkeys key:1 value:1 key:2 value:2 key:3 value:3 key:4 value:4 key:5 value:5 key:6 value:6
# 建一个 order 为键的汇合,并给出程序
zadd order 1 key:3 2 key:1 3 key:2
如果不晓得 hmset 和 zadd 命令的作用,能够参考hmset 和 zadd
执行上面的 Lua 脚本
eval "local order = redis.call('zrange', KEYS[1], 0, -1); return redis.call('hmget',KEYS[2],unpack(order));" 2 order hkeys
你将看到如下的输入后果
“value:3”“value:1”“value:2”
- 通过 zrange 取出 order 汇合外面的数据,即:[key:3 , key:1 , key:2]
- 而后通过 unpack 函数将[key:3 , key:1 ,key:2] 转成 key:3 key:1 key:2
- 最初执行 hmget hkeys key:3 key:1 key:2,所以失去下面的输入后果
四、Lua 脚本预加载
Redis 能够对 Lua 脚本进行预加载,能够通过 script load 命令把 Lua 脚本预加载到 redis 外面。
script load "return redis.call('get', KEYS[1])"
预加载实现之后,你会看到上面的一段输入
“4e6d8fc8bb01276962cce5371fa795a7763657ae”
这是一个具备唯一性的 hash 字符串,这个 hash 就代表着咱们刚刚预加载的 Lua 脚本,咱们能够通过 EVALSHA 命令执行该脚本。如:
evalsha 4e6d8fc8bb01276962cce5371fa795a7763657ae 1 key:name
执行的后果与上面的是统一的。
eval "return redis.call('get', KEYS[1])" 1 key:name
五、一个批改 JSON 数据的例子?
有些开发人员有的时候可能会将 JSON 数据保留在 Redis 外面,咱们先不说这样做是不是一种好的形式,咱们只来谈一下如何通过 Lua 脚本批改 JSON 数据。
失常状况下,你须要批改一个 JSON Object,你须要把它从 redis 外面查问回来,解析它,批改 key 值,而后再将它序列化保留到 redis 外面。这样做有几个问题:
- 高并发场景下无奈保障原子性,另一个线程能够在以后线程获取和设置 Object 操作之间更改这个 JSON 数据。在这种状况下,将失落更新。
- 性能问题。如果您常常进行这样的更改并且 JSON 数据相当大,这可能会成为利用的性能瓶颈。因为你经常性的进行取数据,存数据。
通过在 Lua 中实现下面逻辑,因为 redis 的 Lua 脚本是在服务端执行的,一方面能够保障操作的原子性,解决高并发失落更新的问题,另一方面节俭网络传输同时晋升性能。
上面咱们向 redis 外面保留一个测试 JSON 字符串:obj
set obj '{"a":"foo","b":"bar"}'
当初,让咱们运行咱们的脚本:
EVAL 'local obj = redis.call("get",KEYS[1]); local obj2 = string.gsub(obj,"(".. ARGV[1] .."\":)([^,}]+)", "%1" .. ARGV[2]); return redis.call("set",KEYS[1],obj2);' 1 obj b bar2
local obj = redis.call("get",KEYS[1]);
其中 KEYS[1]=obj,所以返回值obj= '{"a":"foo","b":"bar"}'
local obj2 = string.gsub(obj,"(" .. ARGV[1] .. "\":)([^,}]+)","%1" .. ARGV[2]);
,..
是 Lua 脚本的字符串连贯符号;咱们应用 RegEx 模式来匹配密钥并替换其值,如果对表达式不相熟,自行补课;”%1″ 示意第一个被匹配的子串,”%1″ .. ARGV[2] 等于 “b”:”bar2″, 并应用 gsub 进行替换。- 最初将后果返回,
obj
的 JSON 对象的后果如下:
{"a":"foo","b":"bar2"}
六、总结
我倡议只有在你能证实它能带来更好的性能时才应用 Lua 脚本。如果你只是想要保障 redis 操作原子性,那么能够应用 transactions 事务。不肯定非要应用 Lua 脚本。
此外 redis Lua 脚本不应太长。因为当脚本运行时相当于为被操作对象加锁,其余操作都在期待它实现。如果 Lua 脚本须要相当长的工夫执行,则可能会导致瓶颈而不是进步性能。Lua 脚本在达到超时后进行(默认状况下为 5 秒)。
欢送关注我的博客,外面有很多精品合集
本文转载注明出处(必须带连贯,不能只转文字):字母哥博客 – zimug.com
感觉对您有帮忙的话,帮我点赞、分享!您的反对是我不竭的创作能源!。另外,笔者最近一段时间输入了如下的精品内容,期待您的关注。
- 《手摸手教你学 Spring Boot2.0》
- 《Spring Security-JWT-OAuth2 一本通》
- 《实战前后端拆散 RBAC 权限管理系统》
- 《实战 SpringCloud 微服务从青铜到王者》
- 《VUE 深入浅出系列》