超卖与分布式锁
# 超卖与分布式锁
需配合 JMeter 压力测试
# synchronized 关键字
直接用关键字 synchronized 给方法体加锁,会有两个问题:
- 锁的粗粒度太大,会导致售卖效率不高(可以容忍)
- 在多节点(多台服务器)的情况下,还是会出现【超卖】(不能容忍)
可以用 MySQL 数据库来加锁,但是性能不高。
# Redis 实现分布式锁
使用类型:String 类型
setIfAbsent 就是对应 Redis 的 setnx
// 获取分布式锁
String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + DateUtil.formatDate(dto.getDate()) + "-" + dto.getTrainCode();
// setIfAbsent就是对应redis的setnx
Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 10, TimeUnit.SECONDS);
2
3
4
# 分布式锁常见的问题
# 问题一:没拿到锁的线程把别人的锁给删了
如果加锁的动作放在
try里,释放锁的操作放在finally,并且用户没拿到锁之后抛出异常,finally里面依旧释放锁,就会导致 没拿到锁的线程把别人的锁给删了 的问题。
把释放锁移出 finally 不是最优解,解法有以下两种:
- 加锁的动作不要放在
try里 - 加锁时,将当前线程 ID 放到锁对应的
value中,删除时,先去获取value,比对value值和当前线程 ID 一致才能删除
# 问题二:过期时间短导致锁失效,两个线程一起执行任务,最后超卖
解决方案有:"看门狗"
假设过期时间为 60 秒,当倒计时到 30 秒的时候,自动刷新过期时间,所以永远不会出现超时问题。
如果主线程出现异常,锁的过期时间会一直刷新吗?
不会。这是使用守护线程的好处。
使用守护线程的好处是:会随主线程的结束而结束,所以不会出现一直重置成 60 秒、永不过期的问题。
使用守护线程的方式不需要手写,可以集成
Redisson。
# 使用 Redisson 看门狗解决锁超时的问题
Redisson 可以实现分布式锁,并且自带"看门狗"方案。(项目中比较常用)


# 问题三:Redis 宕机导致锁互斥失效
在 Redis 集群中,Redis 宕机会导致锁互斥失效的问题。
比如,线程 a 拿到了锁,然后执行任务。突然 Redis 宕机了,然后 Redis 集群重新选出了主节点,线程 b 此时发起请求拿到了锁,这就导致了锁互斥失效的问题。
解决方案:使用 Redis 红锁(实际项目中不常用)
红锁的获取方式:
- 假设现在有 5 台机器,那么线程 1 需要获取
ceil(5/2) = 3把锁才算获取锁成功。这样即使有某台 Redis 宕机了,其他线程也获取不了锁了,从而解决 Redis 宕机问题 - 获取的时候还需按顺序获取,以避免造成死锁问题
- 锁宕机之后,机器不能马上重启。假设超时时间为 5 秒钟,则重启时间至少也为 5 秒钟,获取锁的时间也不能超过 5 秒钟
**当锁宕机后,机器不能马上重启或者切换主备。**这是因为锁可能处于一种不确定的状态,如果机器在锁未释放的情况下重启,可能会导致数据不一致或其他问题。
假设超时时间为 5 秒钟,则重启时间至少也为 5 秒钟,这是为了确保在重启之前,锁有足够的时间超时释放。如果重启时间太短,可能会导致锁在机器重启后仍然被占用,从而导致其他线程无法获取锁。
获取锁的时间也不能超过 5 秒钟,这是为了避免线程在获取锁时出现长时间的阻塞。如果获取锁的时间太长,可能会导致其他线程长时间等待,从而影响系统的性能和响应时间。
可阅读:如何解决集群情况下分布式锁的可靠性 | cmty256.github.io (opens new window)
# 问题四:使用红锁可能带来的问题
- 性能问题:红锁需要在多个 Redis 节点上进行通信和协调,这可能会导致性能下降,特别是在高并发环境下
- 复杂性增加:红锁的实现相对复杂,需要处理多个节点之间的通信、故障检测和恢复等问题,增加了系统的复杂性
- 数据不一致性:在分布式环境中,由于网络延迟、节点故障等原因,可能会导致锁的获取和释放出现不一致的情况,从而影响系统的正确性
- 单点故障:如果 Redis 节点出现故障,可能会导致整个分布式锁系统不可用,从而影响系统的可用性
- 成本问题:使用 Redis 分布式锁需要额外的 Redis 节点和网络资源,增加了系统的成本
# 机器人刷票问题
使用 令牌大闸 防止机器人抢票:
- 分布式锁和限流都不能解决机器人刷票的问题,1000 个请求抢票,900 个限流快速失败,另外 100 个有可能是同一个人在刷库
- 没有余票时,需要查库存才能知道没票,会影响性能,不如查令牌余量来得快