专栏首页码农小胖哥的码农生涯Spring Redis中使用Lua脚本实现高并发原子操作

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 个元素,分别是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 then
        return redis.call('SADD', chosen_key, unpack(chosen_list))
    else
        return 0
    end
else
    return 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枚举。

本文分享自微信公众号 - 码农小胖哥(Felordcn),作者:码农小胖哥

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-10-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 一网打尽Redis Lua脚本并发原子组合操作

    Redis 是高性能的 KV 内存数据库,除了做缓存中间件的基本作用外还有很多用途,比如胖哥以前分享的Redis GEO 地理位置信息计算。Redis 提供了丰...

    码农小胖哥
  • 利用Redis的Geo功能实现查找附近的位

    老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点。明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了。赶紧去查相关的技术选型。经过一番折腾,终...

    码农小胖哥
  • 利用Redis的Geo功能实现查找附近的位置

    老板突然要上线一个需求,获取当前位置方圆一公里的业务代理点。明天上线!当接到这个需求的时候我差点吐血,这时间也太紧张了。赶紧去查相关的技术选型。经过一番折腾,终...

    码农小胖哥
  • 用Lua定制Redis命令

    前言 Redis作为一个非常成功的数据库,提供了非常丰富的数据类型和命令,使用这些,我们可以轻易而高效地完成很多缓存操作,可是总有一些比较特殊的问题或需求需要解...

    枕边书
  • 备战金九银十:当你裸辞遇到了面试难,你需要了解一下这些面试题

    又要到金九银十的跳槽季了,为了让更多的小伙伴可以在面试的时候取的更好的offer,不定期都会分享BAT常问面试题,下面这些面试内容你都会了,30K不在话下,由于...

    Android技术干货分享
  • Hello,Java女神

    第一阶段:C++转行,开始学习Java 第二阶段:Java Web,jsp+servlet 第三阶段:Struct Spring 第四阶段:Spring ...

    birdskyws
  • SQL 背包问题

    这是一道简化的背包问题:有一背包能容纳 50kg 的物品,现有 9 种物品(它们的重量分别是 5kg、8kg、20kg、35kg、41kg、2kg、15kg、1...

    白日梦想家
  • uwsgi+nginx部署django项目

    正常我们写完一个 django 项目是需要放到服务器上运行,在本地开发你可以使用django自带的测试服务器 runserver 启动就行,这个 runserv...

    用户4945346
  • 互联网是如何工作的?

    大家对于互联网的使用已经有很长的时间了,但是如果你需要学习相关的知识时才会发现,其实对它的工作原理并不了解。首先互联网可以使得连入网络的机器互相通信,不再是一个...

    一头小山猪
  • innobackupex自动备份脚本 原

    (adsbygoogle = window.adsbygoogle || []).push({});

    阿dai学长

扫码关注云+社区

领取腾讯云代金券