java操作lua脚本

Wesley13
• 阅读 904

java操作lua脚本实例

  1. 前言 在上一篇文章 Redis中使用Lua脚本来实现并发下的原子操作 中我对Lua语言的一些简单的语法及其在Redis中的操作进行了介绍,但是在Java开发中我们还需要进一步的学习才能使这种技术落地。今天就结合Spring Data Redis这个我们经常使用的Redis开发组件来实际尝试一下Lua 脚本。

  2. Lua 实现抽奖 模拟一个抽奖场景,从奖池中进行随机抽奖。规则如下:

中奖的人只能从奖池中抽取。 每个人只能中奖一次。 中奖总人数不能超过奖项的设置数。 生成中奖名单。 规则有了,我们先来分析如何使用Redis实现。Redis提供了SET集合,这种集合有点类似Java中的Set,放无重复的元素而且是无序的,可以满足随机性和奖池候选人的唯一性。同时它还提供了很多操作来满足抽奖的需要。接下来我们进行一一演示。

Redis SET 的一些操作。 基于篇幅我这里只演示一些抽奖可以用的上的Redis操作。

SET添加元素。 添加一个到多个元素,使用SADD命令往lottery中添加多个元素来模拟往奖池中加人。

 127.0.0.1:6379> sadd lottery u1 u2 u3 u4 u5 u6 u7
 (integer) 7
 127.0.0.1:6379> sadd lottery u1
 (integer) 0

如果没有lottey这个key就新建该key,有就直接添加并返回成功添加的元素个数。同时你会发现如果集合中存在了添加的元素是无法被再次添加的。

查询集合中的元素 查询所有元素通过SMEMBERS命令。

 127.0.0.1:6379> smembers lottery
 1) "u2"
 2) "u7"
 3) "u6"
 4) "u4"
 5) "u1"
 6) "u3"
 7) "u5"

随机抽取N个元素 SET集合有两个命令都能满足随机抽取N个元素,分别是SPOP和SRANDMEMBER,它们的区别在于SPOP会将选中的元素从原来的集合中剔除,而SRANDMEMBER不会。我们分别来使用这两个命令来随机从lottery中抽取2个元素来看看。

 127.0.0.1:6379> srandmember lottery 2
 1) "u2"
 2) "u4"
 127.0.0.1:6379> smembers lottery
 1) "u2"
 2) "u7"
 3) "u6"
 4) "u4"
 5) "u1"
 6) "u3"
 7) "u5"
 127.0.0.1:6379> spop lottery 2
 1) "u3"
 2) "u5"
 127.0.0.1:6379> smembers lottery
 1) "u2"
 2) "u7"
 3) "u6"
 4) "u4"
 5) "u1"

就lottery来说,如果你的奖池人数一次性添加的不再增加使用SPOP;如果动态添加,为了保证中奖的人不再次进入奖池应该使用SRANDMEMBER。

抽奖脚本 接下来就是抽奖脚本,我们从lottery中抽出特定的人放入中奖名单,另外一个集合chosen中。

按道理Redis抽奖脚本在Lua中应该是这样的:

 function draw(KEYS,ARGV) 
     
 -- 抽奖逻辑 函数体
     
 end

但是我们只需要编写抽奖逻辑的函数体,然后把函数体写入.lua文件中,在Maven项目中放入META-INF/scripts文件夹中,如图所示:

Redis结合Lua脚本实现抽奖逻辑 约定lua脚本所在的目录

draw.lua的逻辑为:

 --- 简单抽奖脚本  return 结果最终传递给Java 应用
 -- 奖池的key
 local lottery_key = KEYS[1]
 -- 中奖名单的key
 local chosen_key = KEYS[2]
 -- 预定抽奖的人数
 local lottery_count = ARGV[1]
 
 -- 如果预定抽奖的人数大于0才开始抽奖
 if tonumber(lottery_count) > 0 then
     -- 奖池中抽奖 返回的是 被抽中的人组成的数组
     local chosen_list = redis.call('SRANDMEMBER', lottery_key, lottery_count);
     -- 将抽中的人添加到中奖名单中 返回中奖的人数
     if chosen_list then
         return redis.call('SADD', chosen_key, unpack(chosen_list))
     else
         return 0
     end
 else
     return 0
 end

  1. 对应的 Java 代码 Spring Data Redis中的RedisTemplate提供了execute方法来执行Lua脚本,这里我选择使用下面的方法:

    @Override public T execute(RedisScript script, List keys, Object... args) { return scriptExecutor.execute(script, keys, args); }

RedisScript Redis脚本的抽象,用来加载脚本。 keys对应Lua脚本中的KEYS,用来传入Redis的KEY,在Lua脚本中可以通过 KEYS[索引]来取值,例如取第一个值KEYS[1]。 args用来向Lua脚本传递其它的参数,在Lua脚本中可以通过ARGV[索引]来取值。 我们利用draw.lua脚本从Redis的lottery集合中抽取5名幸运者并把他们添加到中奖名单chosen集合中:

 RedisScript<Long> redisScript = RedisScript.of(new ClassPathResource("META-INF/scripts/draw.lua"), Long.class);
 Long chosenCount = stringRedisTemplate.execute(redisScript, Arrays.asList("lottery", "chosen"), Collections.singletonList("5"));
构造RedisScript对象时务必指定返回值对象以保证Lua脚本对象和Java的返回值能对应上,
否则将出现异常。参见org.springframework.data.redis.connection.ReturnType枚举。

  1. 总结 到此Redis利用Lua脚本进行抽奖的整套逻辑就完成了。Lua脚本在Redis中通常是为了保证高并发下的原子性,当你考虑是否需要使用它时应该充分考虑你的业务和架构是否适合使用它。
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Redis+Lua——他叫了外援
    Redis从2.6版本开始引入对Lua脚本的支持,通过在Redis服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务端原子的执行多个Redis命令。Lua    Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。为
Stella981 Stella981
3年前
Redis 脚本
Redis脚本使用Lua解释器来执行脚本。Reids2.6版本通过内嵌支持Lua环境。执行脚本的常用命令为EVAL。语法Eval命令的基本语法如下:redis127.0.0.1:6379EVALscriptnumkeyskeykey...argarg...实例
Stella981 Stella981
3年前
Nginx + lua +[memcached,redis]
精品案例1、Nginxluamemcached,redis实现网站灰度发布2、分库分表/基于Leaf组件实现的全球唯一ID(非UUID)3、Redis独立数据监控,实现订单超时操作/MQ死信操作SelectPollEpollReactor模型4、分布式任务调试Quartz应用
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Bypass ngx_lua_waf SQL注入防御(多姿势)
0x00前言ngx\_lua\_waf是一款基于ngx\_lua的web应用防火墙,使用简单,高性能、轻量级。默认防御规则在wafconf目录中,摘录几条核心的SQL注入防御规则:select.(from|limit)(?:(union(.?)select))(?:from\Winformation_schema\W)这边
Stella981 Stella981
3年前
Redis执行Lua脚本示例
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。2.原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。3.
3A网络 3A网络
2年前
Lua 脚本在 Redis 事务中的应用实践
Lua脚本在Redis事务中的应用实践使用过Redis事务的应该清楚,Redis事务实现是通过打包多条命令,单独的隔离操作,事务中的所有命令都会按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务中的命令要