一、阅读本文前置条件
可以遵循这个链接中的方法在操作系统上安装 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 秒)。
以上就是redis调用lua脚本及使用场景快速掌握的详细内容,更多关于redis调用lua脚本使用场景的资料请关注www.887551.com其它相关文章!