1. 前言

在上一文中我对 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 个元素,分别是SPOPSRANDMEMBER,它们的区别在于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文件夹中,如图所示:

Maven项目中约定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 thenreturn redis.call('SADD', chosen_key, unpack(chosen_list))elsereturn 0end
elsereturn 0
end

这里的逻辑仅仅为了演示用,实际上要根据你的业务进行编写,lua 相关的语法请参考上一文。

3. 对应的 Java 代码

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

@Override
public <T> T execute(RedisScript<T> script, List<K> 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枚举。

4. 总结

到此 Redis 利用 Lua 脚本进行抽奖的整套逻辑就完成了。Lua 脚本在 Redis 中通常是为了保证高并发下的原子性,当你考虑是否需要使用它时应该充分考虑你的业务和架构是否适合使用它,而非为了“炫技”。

好了今天的分享就到这里,我是:码农小胖哥 多多关注

更多干货分享关注下方公众号

往期推荐

醉酒删库:几杯红酒下肚,7小时数据消失...

Spring Boot 监听 Redis Key 失效事件实现定时任务

最完整的Explain总结,SQL优化不再困难

前瞻:在 Java 16 中会带来哪些新特性?

高可用 Prometheus 的常见问题

音效摸鱼还不够爽?试试IDE里打几盘魂斗罗?

深度内容

推荐加入

最近热门内容回顾   #技术人系列

Spring Redis中使用Lua脚本实现高并发原子操作相关推荐

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

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

  2. Redis中的Lua脚本怎么玩

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

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

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

  4. Redis中的Lua 脚本

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

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

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

  6. redis中使用lua脚本

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

  7. Redis中的Lua脚本超时

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

  8. Redis中缓存Lua 脚本

    为什么要缓存 在脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给Redis 服务端,会产生比较大的网络开销.为了解决这个问题,Redis 提供了EVALSHA 命令,允许开发者通过脚本内容的S ...

  9. redis中执行lua脚本命令

最新文章

  1. linux 备份mbr,MBR的备份与恢复
  2. java中什么是同步_Java中,“synchronized”(同步)是什么意思?什么时候应该用synchronized? - Break易站...
  3. matlab画图显示中文
  4. Codeforces Round #470 Div. 1
  5. tcp套接字编程模型
  6. ZCGL大数据项目优化组件布置
  7. 带你深挖Java泛型类型擦除以及类型擦除带来的问题
  8. 国二mysql综合应用题答案_2017年9月全国计算机二级MySQL考试章节练习题
  9. JSP还能撑多久? -- 关于WEB开发的一些思考
  10. PS改变证件照的背景颜色
  11. 统一通信系统解决方案
  12. PS调出怀旧雨中特写的非主流照片
  13. CAD关闭图层快捷键,隐藏显示的CAD图层
  14. PS在处理论文中实物图片的应用
  15. 高斯模糊java代码_Java实现高斯模糊算法处理图像
  16. IIS 6.0 支持Php
  17. 每日C语言代码(The third day)——斐波那契(兔子数列)
  18. python找到一行单词中最长的_如何在文本文件中找到最长的单词?
  19. python歌词分析_用Python分析周杰伦6.5W字的歌词,原来他是这样的人
  20. 空间三角形_教师招聘试讲-小学数学 三角形内角和 教案

热门文章

  1. MySQL 里设置或修改系统变量的几种方法,这个写的非常清晰
  2. CIFS NFS SMB Samba 文件共享协议 介绍
  3. golang 字节切片 数组 字符串 互转
  4. linux shell 文件去除重复行
  5. linux c 字符串查找函数 strstr strcasestr
  6. Linux内存映射--mmap函数
  7. C++中智能指针的设计和使用
  8. 网卡驱动和队列层中的数据包接收
  9. Design Pattern - Composite(C#)
  10. java acm 母牛的故事_acm母牛的故事 的问题