项目背景:最近项目上有一个新的需求,请求时会带有多个用户id,需要返回统一的资源,但是不能和这部分用户的历史查看的资源有重复,为了避免资源大量浪费记录了每个用户id已使用过的资源记录

遇到的问题:在并发情况下获取用户资源记录并更新的时候会有风险,存在同一个用户返回相同资源的情况

解决方案:1.给该部分内容使用redis加锁,但是在加锁时记录每个用户id,需要用到多条redis语句,没办法保证原子性。最后使用了lua脚本对这部分进行操作。

2.使用数据库行锁,但是查询记录是多个不同的表中,分别加锁并不能达到要求。

因为需要保证每次请求都必须有返回结果,所以最终选择了方案1 。将所有的用户id保存到一个set里,有新的请求时去查询是否已经存在这部分用户,如果存在循环等待一直到加上锁,如果不存在就将这部分用户更新到set中

1.准备需要用到的lua脚本

首先是下载lua脚本插件

然后在resource下新建一个目录创建lua脚本文件

 

最后就是脚本文件了

for i, userId in pairs(ARGV) do
    local value = redis.call('SISMEMBER', KEYS[1], userId)
    if value == 1 then
        return 0;
    end
end
for i, userId in pairs(ARGV) do
    redis.call('SADD', KEYS[1], userId)
end
return 1;

 因为逻辑并不复杂,所以脚本也比较简单

2.代码中使用lua脚本

使用静态代码块直接加载脚本

private final static DefaultRedisScript<Long> lock;
static {
    lock = new DefaultRedisScript<>();
    // 指定脚本文件
    lock.setLocation(new ClassPathResource("lua/lock.lua"));
    // 指定返回值
    lock.setResultType(Long.class);
}

这里高版本的jdk会有一个非法反射警告,不用管他

WARNING: An illegal reflective access operation has occurred

在需要加锁的地方使用脚本

List<String> keyList = List.of(RedisKeyConstant.USER.USER_ID);
Long result = (Long) redisTemplate.opsForSet().getOperations()
    .execute(lock, keyList, userIds.toArray());

代码基本上就这些了,因为是第一次使用也遇到了一些坑,简单说明一下

遇到的坑:

1.最开始遇到的肯定是脚本写法方面的坑,不过这部分网上教程很多,磕磕绊绊的基本上都没啥,第一次使用的时候肯定都会遇到

2.返回值接收。一开始就去了解了lua脚本和redis直接的数据结构转换,发现lua中的false在redis中会变成Nil,就想着没办法用boolean类型了,所以改为了整型使用Integer进行接收,结果一直报非法状态异常,一直以为是其他地方的原因,最后偶然发现其他人是用的Long类型接收才发现是数据类型的问题(最后发现boolean类型也能接收,应该是redisTemplate封装过。。。)。

其他的一些都是小问题了,比如在redis客户端对脚本进行测试的时候需要输入密码进行验证,然后就是lua脚本中ARGV必须大写才能识别

总结:总体来说功能完成了,不过学习过程中碰到很多问题都没时间去验证,有时间了再去一一填坑吧,希望能够保持长久的学习热情。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐