SpringBoot整合Lua脚本,实现对Redis的原子操作

前言

最近笔者在鱼皮的网站面试鸭上看到了这么一道题目:怎么实现一个点赞功能?

相信各位读者第一时间应该都是能够想到使用redis来实现这一个功能的,因为对于点赞这一种高频的操作,肯定是不能够直接去访问数据库的,容易将整个数据库压垮,因此需要在用户与数据库之间增加一层缓存,当用户进行点赞操作时,首先会在redis上进行操作,然后再通过定时任务redis中的数据持久化到mysql中就可以实现点赞的功能。

但就在笔者自认为自己很机智的时候,突然被评论区的一位老哥打了脸,这位老哥给出的方案是通过 redis + lua 脚本来实现点赞的功能,笔者看到这个评论的一瞬间先是楞了几秒钟,紧接着就发出了没见过大世面的夺命三连问——lua是个什么东西?为什么需要使用它?我刚刚的方案不是已经很完美了吗?

带着疑问向度娘询问了下lua脚本后,笔者才恍然大悟,还是自己太年轻了。虽然redis默认是单线程,但对于多个key的操作,仍然有可能发生数据不一致的问题,而lua脚本就可以避免这一问题的发生,因为redis每次只能执行一个lua脚本,因此在lua脚本上的redis操作都被捆绑成了一个整体(原子),要么一起执行成功,要么一起执行失败。并且将多个redis的操作捆绑在一起还减少了多次网络请求传递的开销。总之就是一句话,lua脚本很香~!

接下来就由笔者为各位读者分享一下,如何在springboot中使用lua脚本实现一个简易的视频点赞功能。当然,使用lua脚本的前提,还需要各位读者对redis的基本命令以及lua的基本语法都有一定的了解,如果你不够熟悉,可以通过以下的链接先进行学习


1.导入依赖

因为redis默认支持lua脚本,所以我们如果要使用lua脚本,就无需再导入其他的包了,直接导入redis的依赖即可。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.测试代码

执行lua脚本前,需要先实例化DefaultRedisScript对象,通过该对象来执行lua脚本。需要注意的是,在执行execute方法时,第一个参数是脚本的操作对象;第二个参数是redis的key集合,在lua脚本中可以通过KEYS[i]来获取,其中i从1开始数起;后面的所有参数都是value值,在lua脚本中可以通过ARGV[i]获取,同样的,i也是从1开始数起的。

@SpringBootTest
class LuaTestApplicationTests {
    @Autowired
    void setRedisTemplate(RedisTemplate redisTemplate) {
        LuaTestApplicationTests.redisTemplate = redisTemplate;
    }
    private static RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        // 实例化脚本对象
        DefaultRedisScript<Boolean> lua = new DefaultRedisScript<>();
        // 设置脚本的返回值
        lua.setResultType(Boolean.class);
        // 载入lua脚本
        lua.setLocation(new ClassPathResource("thumbs.lua"));
        // key的集合
        List<String> keys = Arrays.asList("user_set", "nums");
        // 执行lua脚本
        System.out.println(redisTemplate.execute(lua, keys, 1));
    }

    @Scheduled(cron = "0 0/10 * * * ? ")
    void toMysql() {
        // 同步到mysql的具体代码.....
    }
}

3.Lua脚本

这里简单解读一下这个脚本的逻辑:首先是通过KEYS[i]ARGV[i]分别接收传递过来的key值和value值,然后判断user_set集合中是否存在当前用户的user_id如果存在,则证明该用户曾对这一视频进行过点赞了,需要执行的是取消点赞的操作,因此需要将该用户的user_iduser_set集合中移除,并且将该视频的总点赞数nums减一;user_set集合中 如果不存在 该用户的user_id,则证明该用户还未对该视频点赞,因此需要将该用户的user_id存入到user_set集合中,并将该视频的总点赞数nums加一;

-- 用于存储点赞用户id的集合
local user_set = KEYS[1]
-- 记录该视频的点赞数
local nums = KEYS[2]
-- 当前执行点赞操作的用户id
local user_id = ARGV[1]

-- 判断set中是否存在此用户的id
if redis.call('sismember', user_set, user_id) == 1 then
    -- 该用户已经点过赞了,因此需要执行取消点赞的操作,总点赞数减一,并将该用户的id移除
    redis.call('srem', user_set, user_id)
    redis.call('decr', nums)
else
    -- 该用户还未点赞,总点赞数加一,并将该用户的id加入集合中
    redis.call('sadd', user_set, user_id)
    redis.call('incr', nums)
end
return true;

这样一来,一个使用 sprinboot + redis + lua 实现的简易点赞功能就完成了~!!

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云丶言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值