前言
最近笔者在鱼皮的网站面试鸭上看到了这么一道题目:怎么实现一个点赞功能?
相信各位读者第一时间应该都是能够想到使用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.setLocation(new ClassPathResource("thumbs.lua"));
List<String> keys = Arrays.asList("user_set", "nums");
System.out.println(redisTemplate.execute(lua, keys, 1));
}
@Scheduled(cron = "0 0/10 * * * ? ")
void toMysql() {
}
}
3.Lua脚本
这里简单解读一下这个脚本的逻辑:首先是通过KEYS[i] 和ARGV[i] 分别接收传递过来的key 值和value 值,然后判断user_set 集合中是否存在当前用户的user_id ,如果存在,则证明该用户曾对这一视频进行过点赞了,需要执行的是取消点赞的操作,因此需要将该用户的user_id 从user_set 集合中移除,并且将该视频的总点赞数nums 减一;user_set 集合中 如果不存在 该用户的user_id ,则证明该用户还未对该视频点赞,因此需要将该用户的user_id 存入到user_set 集合中,并将该视频的总点赞数nums 加一;
local user_set = KEYS[1]
local nums = KEYS[2]
local user_id = ARGV[1]
if redis.call('sismember', user_set, user_id) == 1 then
redis.call('srem', user_set, user_id)
redis.call('decr', nums)
else
redis.call('sadd', user_set, user_id)
redis.call('incr', nums)
end
return true;
这样一来,一个使用 sprinboot + redis + lua 实现的简易点赞功能就完成了~!!
|