一、lua脚本

lua是一种轻量小巧的脚本语言,用标准的C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

lua的详细内容你可以参考lua官方网站:http://www.lua.org/ (lua的官方网站和它的设计理念一样,轻量简洁易上手)

二、redis中的lua

redis从2.6版本开始内置了lua模块,所以在redis服务器中可以直接执行lua脚本。以下用eval命令来演示:

127.0.0.1:6379> eval "return {KEYS[1], KEYS[2], ARGV[1]}" 2 key1 key2 argv1
1) "key1"
2) "key2"
3) "argv1"

1)通过eval命令来执行lua脚本

2)eval后的第一个参数 "return {KEYS[1], KEYS[2], ARGV[1]}" 是lua脚本

3)脚本之后是一个数值,这个数值表示之后的key的个数

4)数值后面是对应的key的

5)除去定义的key的个数,其它值就是额外参数

注意:key和argv在lua脚本中可以通过KEYS[index]和ARGV[index]来获取

上面演示了如何通过eval命令来解释执行lua脚本,那么lua脚本中如何执行redis的命令呢,如:

127.0.0.1:6379> eval "redis.call('set', 'name', 'lay')" 0
(nil)
127.0.0.1:6379> eval "return redis.call('get', 'name')" 0
"lay"

以上,我们使用redis.call()来调用redis命令并通过return 将命令结果返回出来。

但事实上上面这样的做法并不好,因为我们将name和lay这种值直接写在了脚本中,下面我们换一个写法,并解释为什么这样写不好:

127.0.0.1:6379> eval "redis.call('set', KEYS[1], ARGV[1])" 1 name lay
(nil)
127.0.0.1:6379> eval "return redis.call('get', KEYS[1])" 1 name
"lay"

这种写法和之前的区别在于,不再是直接硬编码在代码中,而是通过传入值的方式来执行。

那么这种方式有上面好处呢?

我们知道,redis是一个c/s架构,也就是客户端需要像服务端通过请求传送数据。lua脚本也会在请求过程中传送,那么理所当然的是,服务端为了加快速度就会缓存脚本,如果lua脚本相同就可以不用多次传送直接执行,减少带宽加快传送速度。

但是如果我们直接硬编码在lua脚本中,那么每次只要参数值改变,我们就需要重新传输并解释执行lua脚本,这个是比较浪费资源的。如果我们采用第二种写法,那么参数传递并不会影响脚本缓存。

三、lua和redis的类型转换

在redis和lua的交互中我们看到,数据是可以被传递的,但是redis和lua的数据类型却不一样,所以它的内部肯定存在一个类型转换机制。

redis <-> lua 转换:

1) integer <-> number

2) bulk <-> string

3) multi bulk <-> table(array)

4) status <-> table 携带ok变量

5) error <-> table 携带err变量

6) nil bulk 和multi bulk <-> false

这里要注意:

1) redis中的integer类型和lua中的number类型转换,如果lua中的数值是浮点型,那么会被转换成整型,也就是小数会被去掉,因此我们需要采用字符串型来返回浮点型数据(比如:tostring()来转换)

2) 如果lua返回值中存在nil,会导致转换错误,从而不返回nil之后的数据,如:

127.0.0.1:6379> eval "return {1,2,3,nil,4,5,6}" 0
1) (integer) 1
2) (integer) 2
3) (integer) 3

四、错误

你可以通过redis.error_reply命令,也可以直接返回错误信息数据结构

127.0.0.1:6379> eval "return redis.error_reply('error msg')" 0
(error) error msg
127.0.0.1:6379> eval "return {err='error msg'}" 0
(error) error msg
127.0.0.1:6379> eval "return redis.status_reply('err')" 0
err

lua执行redis命令有两种call()和pcall()他们唯一的区别在于pcall()会进行错误捕获,并返回格式化的信息:

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> lpush foo bar
(integer) 1
127.0.0.1:6379> eval "return redis.call('get', KEYS[1]) 1 foo
Invalid argument(s)
127.0.0.1:6379> eval "return redis.call('get', KEYS[1])" 1 foo
(error) ERR Error running script (call to f_4e6d8fc8bb01276962cce5371fa795a7763657ae): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> eval "return redis.pcall('get', KEYS[1])" 1 foo
(error) WRONGTYPE Operation against a key holding the wrong kind of value

五、evalsha

上面我们频繁使用eval命令来执行lua脚本,从表面上似乎没有上面问题。但你需要注意到的是,eval命令会强制每次发送lua脚本到服务端,即使redis的服务端有着缓存机制,你不需要重新编译脚本,但是你每次执行命令都得为它付出额外的带宽消耗成本。

evalsha命令就是为了处理这样的情况,它从使用上基本和eval是一致的,不过第一个参数是一串加密字符串。evalsha的执行过程如下:

1)通过加密字符串去校验是否存在该脚本

2)如果存在那么执行该脚本

3)如果不存在那么返回不存在错误

除了evalsha命令之外,还有一些其它相关命令,如:

script flush: 清空脚本缓存

script exists sha1 sha2 ...shaN: 判断脚本是否存在

script load script: 将脚本加载到缓存

script kill: 杀死正在进行的脚本

六、脚本复制

redis从3.0开始结束了无集群时代,但是在3.2之前,对于lua脚本的复制是采用复制整个脚本的方式。而在3.2之后采用的是复制脚本生成的单个写入命令,被称作(脚本影响复制)。意思就是,当执行lua脚本的时候,redis会收集由脚本引擎执行的所有实际产生修改数据集的命令。当脚本执行完毕以后,由脚本生成的命令序列将被包装到multi/exec事务中,并发从master发送到slave,以及进行aof持久化保存。

为了启用脚本影响复制,你需要在执行lua脚本之前启用执行以下脚本:

redis.replicate_commands()

当启用脚本影响复制以后,可以使用redis.set_repl(参数)设置复制的方式:

redis.set_repl(redis.REPL_ALL) -- 复制到从slave和aof
redis.set_repl(redis.REPL_AOF) -- 只复制到aod
redis.set_repl(redis.REPL_SLAVE) -- 只复制到slave
redis.set_repl(redis.REPL_NONE) --完全不复制

注意,在设置之前需要开启脚本影响复制,否则会报错

七、可用类库

redis默认加载了以下的类库,你可以直接使用

base lib.
table lib.
string lib.
math lib.
struct lib.
cjson lib.
cmsgpack lib.
bitop lib.
redis.sha1hex function.
redis.breakpoint and redis.debug function in the context of the Redis Lua debugger.

我们以cjson lib为例:

127.0.0.1:6379> eval 'return cjson.encode({["foo"]= "bar"})' 0
"{\"foo\":\"bar\"}"
127.0.0.1:6379> eval 'return cjson.decode(ARGV[1])["foo"]' 0 "{\"foo\":\"bar\"}"
"bar"

我们看到,在lua中可以非常轻易地进行json数据处理,cjson帮助我们简化了json的操作。

八、事务

一个redis脚本被定义为一个事务,所以一切redis的事务特性redis脚本都可以完成,并且通常redis脚本更加地简单快速。这意味着,你不用担心脚本内的逻辑代码执行过程中会因为客户端并发产生问题,因为一定程度上来说脚本是具备原子特性的(虽然事实上它并不完全符合传统意义上的原子性)。

你可能对此表示疑惑,因为这看起来脚本和事务完全是重复的东西。事实上,在2.6之前是没有lua脚本嵌入模块的,但是事务却已经存在很长时间了。redis的官方认为,不愿意去除的原因在于,你使用事务的话,可以以最小的复杂度解决竞态问题,而很多用户都是这么做的。

当然,如果未来很多用户都基于脚本去实现的话,那么官方理所当然地会移除事务,从而只保存脚本。

更多redis的lua内容请参考官网:https://redis.io/commands/eval

redis(6)lua脚本的更多相关文章

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

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

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

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

  3. 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁

    spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...

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

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

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

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

  6. 快速入门Redis调用Lua脚本及使用场景介绍

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

  7. redis中lua脚本的简单使用

    一.背景 在使用redis的过程中,发现有些时候需要原子性去操作redis命令,而redis的lua脚本正好可以实现这一功能.比如: 扣减库存操作.限流操作等等. redis的pipelining虽然 ...

  8. Redis执行Lua脚本的情况

    第一个测试: 往Redis里面存入1000个Hash,每个Hash里面有100个元素(Key 0-99,值是Key^2). PHP代码,执行33s左右 <?php $redis = new Re ...

  9. Redis执行Lua脚本示例

    Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行.使用脚本的好处如下: 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在red ...

  10. redis之lua脚本

    背景介绍 redis数据库提供了一些管理功能比如 流水线:打包发送多条命令,并在一个回复里面接收所有被执行命令的结果.事务:一次执行多条命令,被执行的命令要么就全部都被执行,要么就一个也不执行.并且事 ...

随机推荐

  1. [ActionScript 3.0] 嵌入字体

    首先我们要生成一个swf的字体库,以微软雅黑为例,新建YaHei_font.fla,ctrl+L,在库面板中右键→新建字型,弹出字体元件属性窗口,选择要嵌入的字体, 并选择为ActionScript ...

  2. Objective-C中的meta-class

    讨论Objective-C的一个奇怪的概念 meta-class 在Objective-C中的每个类,都有它自己相关的meta-class,但因为你很少直接使用meta-class,所以显得很神秘.  ...

  3. 使用单个httpclient实例请求数据。

    做J2EE的都知道httpclient这个工具,Android也自带这个工具,不过和J2EE上的不一样,可能是google在添加的时候,自己修改了一部分代码. 在J2EE中可以使用如下代码,在多线程的 ...

  4. 正则表达式 python

    下面这种方式 从结果上看 匹配的是关键字, 但是不是 每一次都可以 100% 准确 search_words_dict = { "肠炎宁": 0, "维生素AD" ...

  5. docker安装MySQL软件

    1 搜索mysql镜像 $ sudo docker search mysql NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql MySQL is a wi ...

  6. 除了ROS, 机器人定位导航还有其他方案吗?

    利用ROS进行机器人开发,我想大多数企业是想借助ROS实现机器人的导航.定位与路径规划,它的出现大大降低了机器人领域的开发门槛,开发者无需向前人一样走众多弯路,掌握多种知识才能开始实现机器人设计的梦想 ...

  7. git特殊用法

    git stash的使用 1.当前分支写了一半的代码,需要切到其他分支修复bug或者完成优先级较高的任务时 git stash 暂存分支进度 git stash list 查看草稿区 git stas ...

  8. LoadRunner性能测试结果分析(转载)

    性能测试的需求指标:本次测试的要求是验证在30分钟内完成2000次用户登录系统,然后进行考勤业务,最后退出,在业务操作过程中页面的响应时间不超过3秒,并且服务器的CPU使用率.内存使用率分别不超过75 ...

  9. javascrpit sort()数组对象中排序

    /*ionic 调用 * @param attr 排序的属性 如number属性 * @param rev true表示升序排列,false降序排序 * */ commonSortMethod(att ...

  10. DP Intro - poj 2342 Anniversary party

    今天开始做老师给的专辑,打开DP专辑 A题 Rebuilding Roads 直接不会了,发现是树形DP,百度了下了该题,看了老半天看不懂,想死的冲动都有了~~~~ 最后百度了下,树形DP入门,找到了 ...