一、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. LeetCode 刷题指南(1):为什么要刷题

    虽然刷题一直饱受诟病,不过不可否认刷题确实能锻炼我们的编程能力,相信每个认真刷题的人都会有体会.现在提供在线编程评测的平台有很多,比较有名的有 hihocoder,LintCode,以及这里我们关注的 ...

  2. 模拟RHCSA考试环境

    转载自 http://blog.51cto.com/10681635/2084794 模拟RHCSA考试环境 第1章  修改 root 密码 第2章  配置网络 第3章  设定SeLinux 第4章  ...

  3. HTML5中本地储存概念是什么,什么优点 ,与cookie有什么区别?

    html5中的Web Storage 包括了两种存储方式: sessionStorage  和  localStorage. seessionStorage 用于本地存储一个会话(session)中的 ...

  4. leecode刷题(11)-- 反转字符串

    leecode刷题(11)-- 反转字符串 反转字符串 描述: 编写一个函数,其作用是将输入的字符串反转过来. 示例 1: 输入: "hello" 输出: "olleh& ...

  5. leetcode-747-Largest Number At Least Twice of Others(求vector的最大值和次大值)

    题目描述: In a given integer array nums, there is always exactly one largest element. Find whether the l ...

  6. Hibernate 自动创建表bug问题解决

    我在hibernate.cfg.xml配置文件中添加了自动创建表的的属性:(这样当数据库中没有此表是,hibernate就会自动帮我们创建一张表) <property name="hb ...

  7. v-model和v-bind的区别

    VUE学习篇1 Mustache (双大括号写法)不能在 HTML 属性中使用,应使用 v-bind 指令: <div v-bind:id="dynamicId">&l ...

  8. JAVA数据结构--二叉查找树

    二叉查找树定义 二叉查找树(英语:Binary Search Tree),也称二叉搜索树.有序二叉树(英语:ordered binary tree),排序二叉树(英语:sorted binary tr ...

  9. 2019 CCPC-Wannafly Winter Camp Day5(Div2, onsite)

    solve 5/11 补题:7/11 A Cactus Draw Code:zz Thinking :zz 题意:要在n*n的网格内画上一棵节点数为n树,使得没有边相交. 很好想的构造题,因为网格有n ...

  10. 剑指offer——面试题18:删除链表的节点

    #include"List.h" void DeleteNode(ListNode** pHead,ListNode* pToBeDeleted) { if(*pHead==nul ...