Spring Redis中使用Lua脚本实现高并发原子操作
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 个元素,分别是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
文件夹中,如图所示:
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脚本实现高并发原子操作相关推荐
- 深入理解redis中的lua脚本
本文来说下redis中的lua脚本 文章目录 概述 Lua简介 使用Lua脚本的好处 Redis+Lua实现限流 本文小结 概述 今天讲一些redis和lua脚本的相关的东西,lua这个脚本是一个好东 ...
- Redis中的Lua脚本怎么玩
Redis中的Lua脚本怎么玩 Lua是一门强大.快速.轻量的嵌入式脚本语言,我们日常开发中接触的最多的还是Redis为保证原子性使用Lua执行多命令的一种方法,那么现在先来熟悉Lua基本用法. Lu ...
- Redis中使用Lua脚本(二)之红包雨的抢夺
Redis中使用Lua脚本(二)之红包雨的抢夺 一.需求介绍 二.红包雨的需求分析及概要设计 三.红包雨的Lua脚本设计及模拟演示 四.Lua脚本在生产环境的使用 一.需求介绍 如同前两年的爆款&qu ...
- Redis中的Lua 脚本
Lua/ˈluə/是一种轻量级脚本语言,它是用C 语言编写的,跟数据的存储过程有点类似.使用Lua 脚本来执行Redis 命令的好处: 1.一次发送多个命令,减少网络开销. 2.Redis 会将整个脚 ...
- Redis中使用Lua脚本(续)- Linux下Lua-cjson开源库的安装和使用
Redis中使用Lua脚本(续)- Lua-cjson开源库的安装和使用 问题 原因 解决方案 在Redis的lua脚本编写中,我们可能会用到json的序列化和反序列化. Json序列化: -- Re ...
- redis中使用lua脚本
一.概述 1.什么是lua脚本 Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放. 其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能.因为广泛的应用于:游戏开 ...
- Redis中的Lua脚本超时
Redis 的指令执行本身是单线程的,这个线程还要执行客户端的Lua 脚本,如果Lua脚本执行超时或者陷入了死循环,是不是没有办法为客户端提供服务了呢? eval 'while(true) do en ...
- Redis中缓存Lua 脚本
为什么要缓存 在脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给Redis 服务端,会产生比较大的网络开销.为了解决这个问题,Redis 提供了EVALSHA 命令,允许开发者通过脚本内容的S ...
- redis中执行lua脚本命令
最新文章
- linux 备份mbr,MBR的备份与恢复
- java中什么是同步_Java中,“synchronized”(同步)是什么意思?什么时候应该用synchronized? - Break易站...
- matlab画图显示中文
- Codeforces Round #470 Div. 1
- tcp套接字编程模型
- ZCGL大数据项目优化组件布置
- 带你深挖Java泛型类型擦除以及类型擦除带来的问题
- 国二mysql综合应用题答案_2017年9月全国计算机二级MySQL考试章节练习题
- JSP还能撑多久? -- 关于WEB开发的一些思考
- PS改变证件照的背景颜色
- 统一通信系统解决方案
- PS调出怀旧雨中特写的非主流照片
- CAD关闭图层快捷键,隐藏显示的CAD图层
- PS在处理论文中实物图片的应用
- 高斯模糊java代码_Java实现高斯模糊算法处理图像
- IIS 6.0 支持Php
- 每日C语言代码(The third day)——斐波那契(兔子数列)
- python找到一行单词中最长的_如何在文本文件中找到最长的单词?
- python歌词分析_用Python分析周杰伦6.5W字的歌词,原来他是这样的人
- 空间三角形_教师招聘试讲-小学数学 三角形内角和 教案