关于使用 redisson 分布式锁出现的问题

2019-06-05 11:43:25 +08:00
 eefnrowe
先贴一段简单 code:

//红包锁 redis_lock:redpacket:${redPacketId}
String lockName = RedisKeys.buildDistributedLockKey("redpacket", redPacketId);
RLock rLock = redissonClient.getLock(lockName);

//用户锁 redis_lock:redpacket_userid:${userId}
String lockUserName = RedisKeys.buildDistributedLockKey("redpacket_userid", userId);
RLock lockUser = redissonClient.getLock(lockUserName);

try {
rLock.lock(10, TimeUnit.SECONDS);
lockUser.lock(10, TimeUnit.SECONDS);

//抢红包
return doUnpack(redPacketId);

} finally {
lockUser.unlock();
rLock.unlock();
}



----------------

问题如果没有加 lockUser 锁的情况下:
用 jmeter 测试 单用户 10 个线程并发, 持续测试 5-10 个用户左右, 就肯定会出现一个人执行了两次业务逻辑的问题,
更加奇怪的是, 我业务逻辑也用 redis 做过重复领取红包的行为判断 用的是 redis set isMember 判断的, 那两次重复执行的请求中, 第二次却没有检查出来!!!

所以我怀疑我是不是把 redis 哪里给玩坏了??

ps: redis 配置很简单 就个单节点~
4246 次点击
所在节点    Java
11 条回复
snappyone
2019-06-05 13:26:47 +08:00
你的目的是一个用户不能抢同一个红包多次吗?你的 isMember 检查是放在什么位置的呢
eefnrowe
2019-06-05 14:02:22 +08:00
@snappyone 当然是放在 doUnpack 方法里靠前的

String userIdsKey = RedisKeys.buildRedPacketUserIdsKey(redPacketId);
if(redisTemplate.hasKey(userIdsKey)){
Boolean member = redisTemplate.opsForSet().isMember(userIdsKey, userId);
if(member){
throw new CommonException(ResponseCode.ERROR, "不可重复领取此红包");
}
redisTemplate.opsForSet().add(userIdsKey, userId);
}else{
redisTemplate.opsForSet().add(userIdsKey, userId);
redisTemplate.expire(userIdsKey, 1, TimeUnit.HOURS);
}
snappyone
2019-06-05 14:27:55 +08:00
@eefnrowe
我感觉是你释放锁的问题,如果一个线程堵塞没拿到锁,在 finally 里面也把这个锁释放了,但是此时拥有锁的并不是他自己。试试 tryLock, 如果加锁失败则不要去释放锁
eefnrowe
2019-06-05 14:55:48 +08:00
@snappyone tryLock 的 waitTime 跟 leaseTime 设值有什么讲究吗
eefnrowe
2019-06-05 15:28:44 +08:00
@snappyone

//红包锁 redis_lock:redpacket:${redPacketId}
String lockName = RedisKeys.buildDistributedLockKey("redpacket", redPacketId);
RLock rLock = redissonClient.getLock(lockName);
try {
boolean b = rLock.tryLock(3, 10, TimeUnit.SECONDS);
if(b){
return doUnpack(redPacketId);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
eefnrowe
2019-06-05 15:29:15 +08:00
@snappyone 一样有类似问题
snappyone
2019-06-05 15:34:34 +08:00
@eefnrowe 我貌似看错了,把 lock 后面的时间看做 waittime 了,不过你可以试下 trylock, 就设置个 5s 就行
eefnrowe
2019-06-05 15:42:36 +08:00
@snappyone 已经试过啦, 等待 3/5 , 10 超时都试过~ 我有点怀疑是其他环节的问题了..
ysweics
2019-06-05 15:44:38 +08:00
把红包和用户的两个 key 组装成一个 key,然后只锁一个 key=redpacketid:userid
eefnrowe
2019-06-05 16:13:10 +08:00
@ysweics 这样红包领取就没有排队机制啦.. 粒度到了用户级别..
734695609
2019-09-12 16:13:58 +08:00
@eefnrowe
你的问题:如果没有加 lockUser 锁的情况下
doUnpack 逻辑有问题。
你只把红包锁住,一个用户的 2 次并发访问,在你的逻辑中,可能都运行到 if(redisTemplate.hasKey(userIdsKey))
都没有 userIdsKey,所以加了两条,
当你在 redisson 中加上用户锁时,一个用户的两次并发请求就不会运行到下面的代码了

我反而有一个问题,在 redisson 的官方文档中
https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8
你在 lock 时

// 加锁以后 10 秒钟自动解锁 官方解释
// 无需调用 unlock 方法手动解锁 官方解释
rLock.lock(10, TimeUnit.SECONDS);

这句代码官方解释是 10 秒后自动解锁,这样的话如果你的业务代码大于 10 秒或者因为某些原因等待超过 10 秒,会不会导致锁被释放掉,锁失效。
所以我感觉不加时间才是对的,除非你业务需要

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/571068

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX