在 Redis 的 2.6 以上版本中,除了可以使用命令外,还可以使用 Lua 语言操作 Redis。从前面的命令可以看出 Redis 命令的计算能力并不算很强大,而使用 Lua 语言则在很大程度上弥补了 Redis 的这个不足。

只是在 Redis 中,执行 Lua 语言是原子性的,也就说 Redis 执行 Lua 的时候是不会被中断的,具备原子性,这个特性有助于 Redis 对并发数据一致性的支持。

Redis 支持两种方法运行脚本,一种是直接输入一些 Lua 语言的程序代码;另外一种是将 Lua 语言编写成文件。

在实际应用中,一些简单的脚本可以采取第一种方式,对于有一定逻辑的一般采用第二种方式。而对于采用简单脚本的,Redis 支持缓存脚本,只是它会使用 SHA-1 算法对脚本进行签名,然后把 SHA-1 标识返回回来,只要通过这个标识运行就可以了。

执行输入 Lua 程序代码

它的命令格式为:

eval lua-script key-num [key1 key2 key3 ...] [value1 value2 value3 ...]

解说:

 eval 代表执行 Lua 语言的命令。Lua-script 代表 Lua 语言脚本。key-num 整数代表参数中有多少个 key,需要注意的是 Redis 中 key 是从 1 开始的,如果没有 key 的参数,那么写 0。[key1key2key3...] 是 key 作为参数传递给 Lua 语言,也可以不填它是 key 的参数,但是需要和 key-num 的个数对应起来。[value1 value2 value3...] 这些参数传递给 Lua 语言,它们是可填可不填的。

这里难理解的是 key-num 的意义,举例说明。

可以看到执行了两个 Lua 脚本。

eval "return'hello java'" 0

这个脚本只是返回一个字符串,并不需要任何参数,所以 key-num 填写了 0,代表着没有任何 key 参数。按照脚本的结果就是返回了 hello java,所以执行后 Redis 也是这样返回的。这个例子很简单,只是返回一个字符串。

eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value

设置一个键值对,可以在 Lua 语言中采用 redis.call(command,key[param1,param2…]) 进行操作,其中:

 command 是命令,包括 set、get、del 等。Key 是被操作的键。param1,param2...代表给 key 的参数。

脚本中的 KEYS[1] 代表读取传递给 Lua 脚本的第一个 key 参数,而 ARGV[1] 代表第一个非 key 参数。

这里共有一个 key 参数,所以填写的 key-num 为 1,这样 Redis 就知道 key-value 是 key 参数,而 lua-value 是其他参数,它起到的是一种间隔的作用。

最后我们可以看到使用 get 命令获取数据是成功的,所以 Lua 脚本运行成功了。

有时可能需要多次执行同样一段脚本,这个时候可以使用 Redis 缓存脚本的功能,在 Redis 中脚本会通过 SHA-1 签名算法加密脚本,然后返回一个标识字符串,可以通过这个字符串执行加密后的脚本。

这样的一个好处在于,如果脚本很长,从客户端传输可能需要很长的时间,那么使用标识字符串,则只需要传递 32 位字符串即可,这样就能提高传输的效率,从而提高性能。

首先使用命令:

script load script

这个脚本的返回值是一个 SHA-1 签名过后的标识字符串,我们把它记为 shastring。通过 shastring 可以使用命令执行签名后的脚本,命令的格式是:

evalsha shastring keynum [key1 key2 key3 ...] [param1 param2 param3 ...]

下面演示过程。

对脚本签名后就可以使用 SHA-1 签名标识运行脚本了。在 Spring 中演示这样的一个过程,如果是简单存储,笔者认为原来的 API 中的 Jedis 对象就简单些,所以先获取了原来的 connection 对象,代码如下所示。

// 如果是简单的对象,使用原来的封装会简易些
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
applicationContext.getBean(RedisTemplate.class);
// 如果是简单的操作,使用原来的Jedis会简易些
Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();
// 执行简单的脚本
String helloJava = (String) jedis.eval("return 'hello java'");
System.out.println(helloJava);
// 执行带参数的脚本
jedis.eval("redis.call ('set', KEYS [1],ARGV [1])", 1, "lua-key","lua-value");
String luaKey = (String) jedis.get("lua-key");
System.out.println(luaKey);
// 缓存脚本,返回shal签名标识
String shal = jedis.scriptLoad("redis.call('set',KEYS[1], ARGV[1])");
// 通过标识执行脚本
jedis.evalsha(shal, 1, new String[] { "sha-key", "sha-val" });
// 获取执行脚本后的数据
String shaVal = jedis.get("sha-key");
System.out.println(shaVal);
// 关闭连接
jedis.close();

上面演示的是简单字符串的存储,但现实中可能要存储对象,这个时候可以考虑使用 Spring 提供的 RedisScript 接口,它还是提供了一个实现类—— DefaultRedisScript,让我们来了解它的使用方法。

这里先来定义一个可序列化的对象 Role,因为要序列化所以需要实现 Serializable 接口,代码如下所示。

public class Role implements Serializable {/*** 注意,对象要可序列化,需要实现Serializable接口,往往要重写serialVersionUID*/private static final long serialVersionUID = 3447499459461375642L;private long id;private String roleName;private String note;// 省略setter和getter
}

这个时候,就可以通过 Spring 提供的 DefaultRedisScript 对象执行 Lua 脚本来操作对象了,代码如下所示。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);
// 定义默认脚本封装类
DefaultRedisScript<Role> redisScript = new DefaultRedisScript<Role>();
// 设置脚本
redisScript.setScriptText("redis.call('set',KEYS[1], ARGV[1]) return redis.call('get', KEYS[1])");
// 定义操作的key列表
List<String> keyList = new ArrayList<String>();
keyList.add("role1");
// 需要序列化保存和读取的对象
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name_1");
role.setNote("note_1");
// 获得标识字符串
String sha1 = redisScript.getSha1();
System.out.println(sha1);
// 设置返回结果类型,如果没有这句话,结果返回为空
redisScript.setResultType(Role.class);
// 定义序列化器
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
// 执行脚本
// 第一个是RedisScript接口对象,第二个是参数序列化器
// 第三个是结果序列化器,第四个是Reids的key列表,最后是参数列表
Role obj = (Role) redisTemplate.execute(redisScript, serializer,serializer, keyList, role);
// 打印结果
System.out.println(obj);

注意加粗的代码,两个序列化器第一个是参数序列化器,第二个是结果序列化器。这里配置的是 Spring 提供的 JdkSerializationRedisSerializer,如果在 Spring 配置文件中将 RedisTemplate 的 valueSerializer 属性设置为 JdkSerializationRedisSerializer,那么使用默认的序列化器即可。

执行 Lua 文件

我们把 Lua 变为一个字符串传递给 Redis 执行,而有些时候要直接执行 Lua 文件,尤其是当 Lua 脚本存在较多逻辑的时候,就很有必要单独编写一个独立的 Lua 文件。比如编写了一段 Lua 脚本,代码如下所示。

redis.call('set',KEYS[1],ARGV[1])
redis.call('set',KEYS[2],ARGV[2])
local n1 = tonumber(redis.call('get',KEYS[1]))
local n2 = tonumber(redis.call('get',KEYS[2]))
if n1 > n2 thenreturn 1
end
if n1 == n2 thenreturn 0
end
if n1 < n2 thenreturn 2
end

这是一个可以输入两个键和两个数字(记为 n1 和 n2)的脚本,其意义就是先按键保存两个数字,然后去比较这两个数字的大小。当 n1==n2 时,就返回 0;当 n1>n2 时,就返回 1;当 n1<n2 时,就返回 2,且把它以文件名 test.lua 保存起来。这个时候可以对其进行测试,在 Windows 或者在 Linux 操作系统上执行下面的命令:

redis-cli --eval test.lua key1 key2 , 2 4

注意:redis-cli 的命令需要注册环境,或者把文件放置在正确的目录下才能正确执行,这样就能看到效果,如图所示。

看到结果就知道已经运行成功了。只是这里需要非常注意命令,执行的命令键和参数是使用逗号分隔的,而键之间用空格分开。在本例中 key2 和参数之间是用逗号分隔的,而这个逗号前后的空格是不能省略的,这是要非常注意的地方,一旦左边的空格被省略了,那么 Redis 就会认为“key2,”是一个键,一旦右边的空格被省略了,Redis 就会认为“,2”是一个键。

在 Java 中没有办法执行这样的文件脚本,可以考虑使用 evalsha 命令,这里更多的时候我们会考虑 evalsha 而不是 eval,因为 evalsha 可以缓存脚本,并返回 32 位 sha1 标识,我们只需要传递这个标识和参数给 Redis 就可以了,使得通过网络传递给 Redis 的信息较少,从而提高了性能。

如果使用 eval 命令去执行文件里的字符串,一旦文件很大,那么就需要通过网络反复传递文件,问题往往就出现在网络上,而不是 Redis 的执行效率上了。参考上面的例子去执行,下面我们模拟这样的一个过程,使用 Java 执行 Redis 脚本代码如下所示。

public static void testLuaFile() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);//读入文件流File file = new File("G:\\dev\\redis\\test.lua");byte[] bytes = getFileToByte(file);Jedis jedis = (Jedis)redisTemplate.getConnectionFactory().getConnection().getNativeConnection();//发送文件二进制给Redis,这样REdis就会返回shal标识byte[] shal = jedis.scriptLoad(bytes);//使用返回的标识执行,其中第二个参数2,表示使用2个键//而后面的字符串都转化为了二进制字节进行传输Object obj = jedis.evalsha(shal,2,  "key1".getBytes(),"key2".getBytes(),"2".getBytes(), "4".getBytes());System.out.println(obj);
}
/**
* 把文件转化为二进制数组
* @param file 文件
* return二进制数组
*/
public static byte[] getFileToByte(File file) {byte[] by = new byte[(int) file.length()];try {InputStream is = new FileinputStream(file);ByteArrayOutputStream bytestream = new ByteArrayOutputStream(); byte[] bb = new byte[2048];int ch;ch = is.read(bb);while (ch != -1) {bytestream.write(bb, 0, ch);ch = is.read(bb);}by = bytestream.toByteArray();} catch (Exception ex) {ex.printStackTrace();}return by;
}

如果我们将 sha1 这个二进制标识保存下来,那么可以通过这个标识反复执行脚本,只需要传递 32 位标识和参数即可,无需多次传递脚本。

从对 Redis 的流水线的分析可知,系统性能不佳的问题往往并非是 Redis 服务器的处理能力,更多的是网络传递,因此传递更少的内容,有利于系统性能的提高。

这里采用比较原始的 Java Redis 连接操作 Redis,还可以采用 Spring 提供的 RedisScript 操作文件,这样就可以通过序列化器直接操作对象了。

Redis中使用Lua语言相关推荐

  1. Redis中的Lua 脚本

    Lua/ˈluə/是一种轻量级脚本语言,它是用C 语言编写的,跟数据的存储过程有点类似.使用Lua 脚本来执行Redis 命令的好处: 1.一次发送多个命令,减少网络开销. 2.Redis 会将整个脚 ...

  2. 深入理解redis中的lua脚本

    本文来说下redis中的lua脚本 文章目录 概述 Lua简介 使用Lua脚本的好处 Redis+Lua实现限流 本文小结 概述 今天讲一些redis和lua脚本的相关的东西,lua这个脚本是一个好东 ...

  3. Redis中的Lua脚本怎么玩

    Redis中的Lua脚本怎么玩 Lua是一门强大.快速.轻量的嵌入式脚本语言,我们日常开发中接触的最多的还是Redis为保证原子性使用Lua执行多命令的一种方法,那么现在先来熟悉Lua基本用法. Lu ...

  4. redis中使用lua脚本

    一.概述 1.什么是lua脚本 Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放. 其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能.因为广泛的应用于:游戏开 ...

  5. Redis中使用Lua脚本(续)- Linux下Lua-cjson开源库的安装和使用

    Redis中使用Lua脚本(续)- Lua-cjson开源库的安装和使用 问题 原因 解决方案 在Redis的lua脚本编写中,我们可能会用到json的序列化和反序列化. Json序列化: -- Re ...

  6. Redis中使用Lua脚本(二)之红包雨的抢夺

    Redis中使用Lua脚本(二)之红包雨的抢夺 一.需求介绍 二.红包雨的需求分析及概要设计 三.红包雨的Lua脚本设计及模拟演示 四.Lua脚本在生产环境的使用 一.需求介绍 如同前两年的爆款&qu ...

  7. Spring Redis中使用Lua脚本实现高并发原子操作

    1. 前言 在上一文中我对 Lua 语言的一些简单的语法及其在 Redis 中的操作进行了介绍,但是在 Java 开发中我们还需要进一步的学习才能使这种技术落地.今天就结合Spring Data Re ...

  8. Redis中的Lua脚本超时

    Redis 的指令执行本身是单线程的,这个线程还要执行客户端的Lua 脚本,如果Lua脚本执行超时或者陷入了死循环,是不是没有办法为客户端提供服务了呢? eval 'while(true) do en ...

  9. redis中的lua

    1.脚本执行 执行lua脚本的命令有 EVAL script numkeys [key [key ...]] [arg [arg ...]] EVALSHA sha1 numkeys [key [ke ...

最新文章

  1. GAN简介及其常见应用
  2. oracle 只读同名词,Oracle创建只读用户,授予视图/同义词/会话权限
  3. Android2.3解析json出错
  4. Multiple Object Tracking:多目标跟踪综述
  5. 在Windows服务器上开启SNMP代理程序
  6. 农行校招考试计算机类,农行校招:还有4个月,流程是这样!
  7. OpenEjb使用笔记--让Tomcat可以部署EJB
  8. 给一维数组输入6个整数,假设为5,7,4,8,9,1
  9. 聊一聊promise的前世今生
  10. px ,em ,rem
  11. Java集合中的细节
  12. 城市天际线 android,都市天际线安卓手机版
  13. base6 python 字节_Python使用base64模块进行二进制数据编码详解
  14. dll domodal运行时异常_解决装备疑难,计算机丢失***.dll文件方法「设计画圈」
  15. 太原理工大学ACM队简介(2018版)
  16. 让驰骋工作流程引擎 ccbpm使用自定义表单来实现自己的业务逻辑.
  17. WPF+prism框架实战源码和展示
  18. 线性调频信号的时频域分析
  19. Vue.js 菜鸟教程 思维导图
  20. HDU-4747 二分+线段树

热门文章

  1. 如何反映两条曲线的拟合精度_用水平仪如何检测导轨的直线度?
  2. 微型计算机的系统组成图,微型计算机系统结构图.doc
  3. 韵乐x5效果器ktv最佳参数_家庭ktv(卡拉ok)家庭影院ktv选购推荐攻略
  4. Win7旗舰版打不开任务管理器怎么办
  5. 多路RTSP-RTMP转RTMP定制版
  6. springboot+shiro:ShiroConfiguration配置
  7. Mybatis 插入时获取主键的方式
  8. unbuntu cmake安装mysql_ubuntu下编译安装mysql5.5
  9. 数据转换之 Number
  10. linux 文件读取 监控,linux 文件系统的监控