Redis 是一种非常流行的内存数据库,常用于数据缓存与高频数据存储。大多数开发人员可能听说过redis可以运行 Lua 脚本,但是可能不知道redis在什么情况下需要使用到Lua脚本。

一、阅读本文前置条件

二、为什么需要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脚本字符串后面的三个参数依次是:

  1. redis Lua脚本所需要的KEYS的数量 ,只有一个KEYS[1],所以紧跟脚本之后的参数值是1
  2. Lua 脚本需要的参数KEYS[1]的参数值,在我们的例子中值为key:name
  3. 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命令的作用,可以参考hmsetzadd

执行下面的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里面。这样做有几个问题:

  1. 高并发场景下无法保证原子性,另一个线程可以在当前线程获取和设置Object操作之间更改这个JSON数据。在这种情况下,将丢失更新。
  2. 性能问题。如果您经常进行这样的更改并且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脚本及使用场景介绍的更多相关文章

  1. 使用redis调用lua脚本的方式对接口进行限流

    java端实现: //初始化一个redis可执行的lua DefaultRedisScript<List> defaultRedisScript = new DefaultRedisScr ...

  2. Redis进阶实践之八Lua的Cjson在Linux下安装、使用和用C#调用Lua脚本

    一.引言         学习Redis也有一段时间了,感触还是颇多的,但是自己很清楚,路还很长,还要继续.上一篇文章简要的介绍了如何在Linux环境下安装Lua,并介绍了在Linux环境下如何编写L ...

  3. 新姿势!Redis中调用Lua脚本以实现原子性操作

    背景:有一服务提供者Leader,有多个消息订阅者Workers.Leader是一个排队程序,维护了一个用户队列,当某个资源空闲下来并被分配至队列中的用户时,Leader会向订阅者推送消息(消息带有唯 ...

  4. redis使用Lua脚本

    最近在看<Redis入门指南>第二版,感觉收获挺大,推荐大家有时间看一看.其中有一章讲Lua脚本,感觉挺实用,把总结整理一下. Redis在2.6中推出了脚本功能,允许开发者使用Lua语言 ...

  5. Redis结合Lua脚本实现高并发原子性操作

    从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis … 案例-实现访问频率限制: 实现访问者 $ip 在一定的时间 $time 内只能访问 $limit 次. 非脚 ...

  6. 用C#调用Lua脚本

    用C#调用Lua脚本 一.引言 学习Redis也有一段时间了,感触还是颇多的,但是自己很清楚,路还很长,还要继续.上一篇文章简要的介绍了如何在Linux环境下安装Lua,并介绍了在Linux环境下如何 ...

  7. .Net Core使用分布式缓存Redis:Lua脚本

    一.前言 运行环境window,redis版本3.2.1.此处暂不对Lua进行详细讲解,只从Redis的方面讲解. 二.Redis的Lua脚本 在Redis的2.6版本推出了脚本功能,允许开发者使用L ...

  8. 要想用活Redis,Lua脚本是绕不过去的坎

    前言 Redis 当中提供了许多重要的高级特性,比如发布与订阅,Lua 脚本等.Redis 当中也提供了自增的原子命令,但是假如我们需要同时执行好几个命令的同时又想让这些命令保持原子性,该怎么办呢?这 ...

  9. PHP中使用redis执行lua脚本示例

    摸索了一下在PHP中如何使用redis执行lua脚本,写了一个脚本如下,供以后参考 <?php $redis = new Redis(); #实例化redis类 $redis->conne ...

随机推荐

  1. docker第一日学习总结

    查看当前所有的镜像 docker images 查看当前运行的容器 docker ps 一般容器分为后台驻留和闪退(ubuntu\busybox等)两种,对于后台驻留的,我们如果想进入这个容器(前提是 ...

  2. linux虚拟摄像头vivid配置

    总述    最近在看摄像头驱动,需要配置虚拟摄像头的驱动,但是教程里面是linux2.6内核的,实际电脑的是Ubuntu16,内核是linux4.15版本,从2.6到4.15内核好多文件发生了变化,所 ...

  3. HDU 6762 Mow (2020 Multi-University Training Contest 1 1012) 半平面交

    Mow 题目链接 分析 将多边形的边向内部缩 r 个单位长度,然后这些边所围成的内部区域,就是圆心的合法范围,该范围也是一个多边形,假设面积是\(a\),周长是\(b\),那么可以知道圆可以覆盖的面积 ...

  4. Codeforces Round #627 (Div. 3) B - Yet Another Palindrome Problem(逻辑)

    题意: 问一个数组中是否存在至少长为3的回文子数组(按下标排列,可不连续). 思路: 找三个相同数或两个不连续的相同数. #include <bits/stdc++.h> using na ...

  5. hdu1011 Starship Troopers

    Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submissio ...

  6. SPOJ 227 Ordering the Soldiers

    As you are probably well aware, in Byteland it is always the military officer's main worry to order ...

  7. Jenkins 安装与部署详细教程

    一.概述 Jenkins 的前身是 Hudson 是一个可扩展的持续集成引擎.Jenkins 是一款开源 CI&CD 软件,用于自动化各种任务,包括构建.测试和部署软件.Jenkins 支持各 ...

  8. codeforces 5C

    C. Longest Regular Bracket Sequence time limit per test 2 seconds memory limit per test 256 megabyte ...

  9. axios增加自定义headers,页面上出现,服务端收不到

    问题 axios增加自定义headers,页面上出现,服务端收不到 原因 vue-cli起的服务是用node-http-proxy中间件处理的 默认是只有几个常用的header,自定义header是直 ...

  10. 关于FFT的一些理解,以及如何手工计算FFT加深理解和验证正确性

    总结缺少逻辑性和系统性,主要便于自己理解和记忆 关于一维FFT的 于是复系数Cn是图像傅里叶变换的yn列向量 于是我们看到最后引入,Cn这个复系数的模来表征频率波的振幅记为Sn(即简谐波叠加的数量 然 ...